Design: Claude cloud sessions, and contract v2 - #603
Conversation
Adds the design spec for creating, watching, steering, and landing Claude Code sessions that run on Anthropic's hosted infrastructure. Four changes ship together: - Contract v2: snapshots declare whether they are complete (the v1 `gone` rule tombstones live sessions when a provider can only enumerate part of its inventory); a `transcript` capability distinct from `log`, for providers whose conversation is structured messages rather than scrollback; a `land` capability; and `stop` demoted from required to declared, since a provider whose sessions are platform-reclaimed cannot implement it. - A `claude-cloud` provider compiled into the daemon as a second `RemoteProviderInvoking` conformance, with a ledger of locally launched sessions unioned with discovery. - A land bridge turning a remote session into a local worktree, showing a fork as a fork. - Repo-declared remotes in a committed, inert `.tbd-remotes.json`, with this repository's own declaration as the worked example. Gated behind a new default-off `claude_cloud_enabled` config column. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xU92u65ynrwqnugtegCab
Attach is the one verb a daemon-compiled provider cannot serve in-process: the app spawns it on the pane's PTY and its exit code never reaches the daemon's runner, so a built-in provider has no executable to hand over. Handing the app a bare vendor argv is the alternative the contract rejects, and the reason that survives for a built-in provider is exit codes. RemoteAttachExitClass reads the attach process's exit through ProviderFailureClass, which the vendor CLI does not speak — an account ineligible for interactive attach would classify as transient and retry forever against a permanent condition. The attach argv becomes a new `tbd remote-attach` subcommand that execs the vendor CLI on the inherited PTY and translates its outcome into contract exit classes. TBDCLI is already installed, so this adds no install step and keeps the contract's live-shim property. Also records that attach and transcript are two windows onto one server-stored conversation rather than a fork, unlike landing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xU92u65ynrwqnugtegCab
A fresh-context review verified the spec's codebase claims and found several that source contradicts, plus three problems that were design faults rather than wording. Security. The repo declaration was described as inert data that cannot cause execution. It is not: repo-declared params are matched against the provider's create_params, which include `prompt`, so an unreviewed declaration could set the opening instruction of an agent holding repo write access and the user's credential — and a single declared remote silently became the default location. Declarations now take effect only after a trust-on-first-use gate that shows every param value verbatim and stores the approved content hash, so any edit re-prompts. The authoritative copy is the registered root checkout's, not the selected worktree's. Attach. The exit-code-translating shim had no lawful input: the signal distinguishing an ineligible account from a dropped transport is terminal output, which both the no-TUI-scraping rule and the contract forbid parsing. The shim is gone; the vendor CLI is spawned directly, eligibility becomes a cached preflight, and RemoteAttachExitClass gains a `permanent` case, since today `.permanent` collapses into `.unexpected` and arms auto-reconnect. Ledger. A ledger row omitted by a complete snapshot is now retired rather than re-asserted forever, ledger-only rows report `state: unknown` instead of fabricating liveness, and `complete: false` no longer stamps freshness — which would have re-opened the mutation gate that 2026-08-01-remote-stale-snapshot closed. Also: idempotency rewritten against the as-built handler, which mints a fresh key per call and retries once; the reserved name skips its entry instead of throwing the whole registry file; land validates provider-supplied branch and remote_url before they reach git; remote transcripts get a TBD-owned root rather than colliding with the Claude projects path guard; flag composition stated explicitly; and v2 contract negotiation noted as machinery to build. Adds archive as a third Session axis with archive/unarchive capabilities, giving remote sessions the lifecycle vocabulary local worktrees already have, including both revive modes. Records the single-account assumption that puts concurrent drivers out of scope. Corrects the claim that Package.swift's platforms declaration blocks a Linux build; the real blockers are AppKit/SwiftUI, Security, and os.Logger. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xU92u65ynrwqnugtegCab
Adds a guide for people maintaining remote agent provider implementations, covering what contract v2 changes and what they need to do about it. It leads with the point that matters most: a v1 provider is already a valid v2 provider with no code changes, because v2 adds no required verbs and makes one less required. The whole adoption step is declaring contract_versions [1, 2]. Every new capability is opt-in, and each section states what happens if it is ignored. Covers `complete` on list and snapshot envelopes, `archived` on the Session object, the `archive`/`unarchive`, `transcript` and `land` capabilities, `stop` becoming optional, and version negotiation. Ends with an ordered checklist that puts correctness ahead of features. Notes one constraint that follows from v1 requiring `stop`: a provider that drops it entirely can no longer claim v1, so it declares [2] rather than [1, 2]. The normative contract revision follows in a separate commit; this guide exists so the normative text can state v2 as it stands without carrying change notes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xU92u65ynrwqnugtegCab
Makes the normative contract state v2 as it stands, with no change notes — the migration guide added alongside it is where a v1 implementer learns what is different. Three verbs are required rather than four: `stop` is capability-gated, and its meaning narrows to ending the running compute. Retiring a session from the inventory is the new `archive`/`unarchive` pair, replacing v1's rule that archival was state the caller kept on its own side. A platform that reclaims idle sessions with no client-facing kill must not declare `stop`, and a caller must not offer an undeclared capability. Snapshots carry `complete`. An incomplete snapshot may adopt and update, but must not retire anything and must not count as refreshing freshness — the drift rule now reads "two consecutive successful complete snapshots", and presence in an incomplete snapshot still resets accrued absences, since a positive observation is real evidence whatever else the provider could not see. Completeness is with respect to the full set including archived sessions, so an active-only enumerator reports false. Sessions carry `archived` as a third axis, orthogonal to liveness and attention, and archived sessions must stay in `list` — filtering them makes them look absent to the drift rule and denies the caller the inventory a revive flow browses. Adds `transcript`, returning agent transcript JSONL on stdout with its continuation cursor as a JSON envelope on stderr; that split is the single exception to "stderr is diagnostic only" and exists so a truncated data stream stays recognizable as truncated. Adds `land`, with the validation a caller must perform on `branch` and `remote_url` before either reaches git. Records the conformance asymmetry around dropping `stop`: declaring a major means conforming to it, so a provider that removes the verb declares [2] alone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xU92u65ynrwqnugtegCab
Handoff — for a session running on a MacThis branch was driven from a Claude Code cloud session, which is Linux. No Swift was written, deliberately. The implementation plan lived in Two prerequisitesP1 — a machine that can build. Everything below needs a real build and test run. P2 — capture the undocumented endpoints. Phases 7 and 8 call claude.ai endpoints nobody has written down. Someone with the account has to observe what the official clients do and record, for discovery, transcript fetch, and archive/unarchive: method, path, auth header shape, request and response bodies, pagination, and how the session status field enumerates. Phases 1–6 don't need this and are most of the feature. One assumption was granted rather than tested: that PhasesTest-first throughout; each task is a standalone commit that builds and passes. 1 gates everything; 2 gates 4–7; 3, 5 and 6 are independent of each other. 1 — Contract v2 plumbing. Negotiated version becomes provider state: 2 — Migrations, each following the shared-model rule in one commit: 3 — Attach. 4 — Land bridge. Validation as a pure function first: ref-name pattern on 5 — Repo-declared remotes. Parse 6 — Built-in provider, documented half. Dispatcher routing 7 — Built-in provider, undocumented half. (needs P2) Client behind a protocol so no test hits the network; discovery; the three union rules; 8 — Transcript. (8.1–8.3 don't need P2) Judgment calls a human may want to revisitThree things were decided without explicit sign-off and are cheap to change:
Conventions this branch is already followingVerify with Generated by Claude Code |
This comment has been minimized.
This comment has been minimized.
…window Addresses the review gate's findings on the design spec. The spec claimed both RPCs reaching TranscriptParser.parse enforce that the path lives under the Claude projects store. Only handleSessionMessages does. handleTerminalTranscript reads a terminal row's transcriptPath verbatim, and the only validation that field ever receives is an absoluteness check at write time in the sessionEvent handler. The guarded call site's own comment asserts it shares a trust boundary with the unguarded one, which is how the asymmetry stays invisible — and is where the spec's claim came from. This matters because the plan said the guard would be "widened" to admit a second root for remote transcripts, which presumed a guard uniformly present to widen. handleTerminalTranscript gains a boundary check it does not have today, and the spec now states that closing the asymmetry is a prerequisite rather than a side effect: a second permitted root is only safe where a boundary is actually checked. Also names the pending-create resolution window at ten minutes with the trade-off stated, since every other timing constant in the document is named precisely and an unnamed compiled duration is a decision nobody visibly makes. Too short strands a slow-provisioning session outside the inventory; too long leaves a failed create pending with nothing to act on. Specifies the ref-name grammar for `land`'s branch validation in the normative contract, so the protection third-party providers must conform to is reviewable from the documents rather than left to each caller's judgement. Corrects a miscount of RemoteProviderRegistry.load's call sites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xU92u65ynrwqnugtegCab
|
All four findings addressed in HIGH — transcript guard. Verified against source; the spec was wrong. Only The root cause is worth recording: the guarded call site's own comment says it enforces "the same trust boundary as The spec now says MEDIUM — pending-create window. Named at ten minutes, with the trade-off stated rather than just the number: too short strands a slow-provisioning session outside the inventory, since capacity is allocated on demand and a setup script runs before the session is usable; too long leaves a genuinely failed create sitting as Minor — ref-name grammar. Fair, and the reason given is the right one: third-party providers have to conform to it, so leaving it unspecified made the protection unreviewable from the documents. The normative contract now spells out the grammar under Minor — call-site miscount. Fixed: three call sites, two swallowing with One thing this surfaces that is out of scope here and needs a decision separately. I haven't touched it: this branch is docs-only, and the session that produced it runs on Linux where TBD cannot be compiled or tested, so any fix would be unverified. Flagging it for a decision — fold it into the implementation work as Phase 8's prerequisite, or take it separately as a fix that stands on its own merits. Generated by Claude Code |
This comment has been minimized.
This comment has been minimized.
The ten-minute timeout is a compiled numeric threshold gating a failure judgment, argued for without running theory-placement's battery — while the section two paragraphs above cites that battery explicitly for the decision to compile the provider itself. Running it: the tunable-number test does flag the shape, but the two-reasonable-projects test is what separates this from the supervision thresholds it resembles. "When is an agent stuck" is contested because one repository's forty idle minutes is another's normal test run; how long a vendor takes to provision a machine is a property of that vendor's infrastructure, not of anyone's working style. The named-consumer test agrees — no repository wants a different provisioning-latency assumption, and a real change in latency should change the value for everyone rather than produce a knob. It stays compiled as a fact TBD asserts about a vendor. That argument does not buy being right, so the constant now ships with the floor its failure mode requires: a session appearing in a later complete snapshot that matches a pending row already cleared as failed is adopted rather than ignored, and the adoption is logged. A window set too short costs a delayed sidebar row instead of a lost session, and the log is the evidence that would justify changing the number. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xU92u65ynrwqnugtegCab
This comment has been minimized.
This comment has been minimized.
The pending-create row said a timed-out create is marked failed "and cleared", while the paragraph after it matched a late-arriving session against a row already cleared — two incompatible mechanisms. The row now transitions to failed and stops asking for attention immediately, but is retained for 24 hours before deletion, which is what gives the late-adoption floor something to match. The built-in provider implements no `stop`, and major 1 requires it, so its describe reports contract_versions [2] rather than the [1, 2] idiom every other example in these documents shows. That is the one place copying the common form would be wrong, and it was never stated. Defines the `environment` create param, which was used in the declaration example without ever being specified: it names a cloud environment configured on the account, typed string rather than enum because describe answers offline and the set is only knowable from the account, with absent meaning the account default and an unrecognized name being the provider's error to report. Gives the resolution order the rationale the document's other contestable decisions get. A repository's declaration outranking the user's global default is not defensible on its own — what settles it is the trust gate, since a declaration only resolves after the user read and approved it, making tier 2 an accepted suggestion rather than a stranger's file. The ordering and the gate are one mechanism. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xU92u65ynrwqnugtegCab
|
Both failures are First attempt — neither specialist reported: After re-running failed jobs, one of the two got through: Two things follow. The conventions lens returned zero findings on this head, so the four items from the previous round are resolved as far as that specialist is concerned; only correctness has yet to report. And the partial improvement between attempts — nothing, then one of two — is consistent with the race the error message itself names, rather than a deterministic failure. Worth noting the same diagnostics block in all three preceding reviews reported the orchestrator's Why this can't be fixed here. The job does I have re-run the failed jobs a second time. If it goes red again for the same reason, this needs a human — either an orchestrator fix on Generated by Claude Code |
This comment has been minimized.
This comment has been minimized.
The transcript boundary was described per-RPC and named two handlers. There are three reads of a terminal row's transcriptPath — parse, parseTail, and lookupDetail serving item-full-body requests — and enumerating them is what produced the current state: the guard was written for one call site and the others were added without it. The boundary now lands at a single resolver every read passes through, so a future entry point cannot start unguarded by default. The completeness rule conflated two questions and would have created a trap. apply(snapshot:) stamps lastSuccessfulSnapshotAt and calls markHealthy together; withholding both on an incomplete snapshot means a provider whose steady state is complete:false never recovers from one transport failure — health stays degraded, hasStaleSnapshot stays true, and Create and Send are blocked permanently, the opposite of the degradation this design claims. Health asks whether the provider is reachable, which an answered call establishes whatever it could enumerate; freshness asks when TBD last held a full inventory, which a partial view does not. Incomplete snapshots now clear health and never advance freshness. Specifies how the contract's keystroke-oriented send byte stream becomes a message for a session with no terminal: a single trailing carriage return or newline is stripped as the submit gesture, interior newlines are preserved, and the caller's side of the wire is unchanged. Hashes the declaration's exact bytes rather than a canonicalized form. Any normalization that reordered the remotes array would let a committer change which remote is offered first — the default — without moving the hash or re-prompting, defeating the gate at the decision it exists to guard. Names the soft edge in the pending-create argument: a repository-authored setup script drives provisioning latency, so a setup-script-heavy repository is a real named consumer of a longer window. The floor answers it rather than a knob — that repository gets a row that arrives late and is then correct, bounding the cost of a wrong constant to latency rather than correctness. Also justifies the 24-hour retention against the horizon the contract already asks providers for. Adds the missing test bullets for double-click create protection and send terminator handling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xU92u65ynrwqnugtegCab
This comment has been minimized.
This comment has been minimized.
…t gaps The ledger retired a row after a single complete snapshot omitted it, while the mirror's gone rule and the normative contract both require two consecutive absences for the stated reason that transports flake and a snapshot can call itself complete having transiently missed something. Retiring on one absence is worse in the ledger than in the mirror: the ledger is what keeps TBD-launched sessions visible when discovery is unavailable, so a row dropped on a flake takes that fallback with it. Now two consecutive complete snapshots, with pending rows exempt — they carry no session id to match and are governed by the ten-minute window alone. TBD_CONTRACT_VERSION was documented as carrying the negotiated major, but describe is the call that produces the negotiation, so no negotiated value exists when it runs. describe now carries the caller's own highest supported major, and a provider must not vary its response based on it: describe answers statically and reports every major it supports, so a caller ahead of the provider still negotiates down instead of being told only what it asked about. land's forks field defined only its true branch. false now has stated caller behavior — one conversation, no divergence presented, and no second landing, since two local copies of a non-forking session would be two writers on one conversation. A provider that cannot guarantee continuity must report true; over-reporting false silently loses work. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xU92u65ynrwqnugtegCab
This comment has been minimized.
This comment has been minimized.
The plan named ProviderRunner as the sole place hardcoding TBD_CONTRACT_VERSION=1. There are three. ProviderEventsSupervisor spawns the events stream outside the runner, and RemoteAttachTerminalView is in TBDApp — it spawns attach directly on the pane's PTY and never passes through RemoteProviderInvoking at all. That third site is what makes this more than a parameter change. Negotiated state held in the daemon's RemoteProviderManager is invisible to the app, so built as described, attach would announce major 1 forever while every other verb for the same provider had negotiated 2 — the divergence the comment above that call site already warns against. The negotiated major now reaches all three: a parameter on the invocation protocol, the same value threaded to the events supervisor, and a field on RemoteProviderStatus, which the app already receives over remote.providers and already has in hand where it builds the attach environment. Part 4's only degrade path covered declarations naming an unregistered provider, which a compiled-in provider is not. A declaration naming a built-in provider whose flag is off now degrades identically, and its trust prompt is never shown — approving a declaration for a feature that cannot run would display a prompt value for nothing and record a decision the user never got to act on. Corrects the ten-minute check to the date seam: it compares a persisted creation timestamp against now, which is data, not a Duration. Drops a misattributed projectsBase citation in favour of stating the property that actually holds — a root TBD owns is not one the session scanner searches. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xU92u65ynrwqnugtegCab
This comment has been minimized.
This comment has been minimized.
The ledger bullet still said a complete snapshot omitting a row retires it, from before the rule became two consecutive absences — an implementer working from the testing section alone would have built exactly the single-absence retirement the rule exists to prevent. Auditing for the same drift found more of it, since several rules acquired prose without acquiring a test: - version negotiation had no bullet at all, including the cross-process case that is the whole reason it is hard: the app-side attach environment must emit the same negotiated major as the daemon's runner and events supervisor, and it is the one that would silently keep announcing 1. - the transcript boundary had no bullet, so the choke point that replaced per-call-site guards was untested — it now asserts the refusal holds for parse, parseTail and lookupDetail alike, which is the property that stops a later entry point from starting unguarded. - land tested only the forks:true path; forks:false forbids a second landing. - declarations did not test that a reorder-only edit re-prompts, which is the attack raw-byte hashing exists to stop, nor that a flag-disabled built-in provider degrades without showing an approval prompt. - 24-hour retention was relied on by the late-adoption test without being asserted anywhere. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xU92u65ynrwqnugtegCab
This comment has been minimized.
This comment has been minimized.
They are not. This PR gives describe a new pre-negotiation behavior — it receives the caller's own highest supported major rather than v1's hardcoded 1 — and the guide opened by telling implementers the opposite. Telling a v1 implementer exactly this class of thing is the document's entire purpose. A second passage in the same section said the negotiated major rides on "every subsequent invocation" without ever saying what describe itself sees, which reads as an ellipsis rather than the gap it was. Both now state the describe-time case explicitly, along with the MUST-NOT-vary rule that bounds it: a caller supporting a newer major than the provider still needs the provider's full contract_versions list to negotiate down, so tailoring the describe response to what the caller announced is how a provider ends up unusable by the caller it was accommodating. The branching advice at the end of the section now names describe as the place where reading the variable is actively wrong rather than merely unnecessary. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xU92u65ynrwqnugtegCab
|
🧌 Changes requested Docs-only PR (contract v2 bump, migration guide, and the Claude cloud-sessions design spec). One MEDIUM finding survived merge; no HIGH findings, no convention violations. MEDIUM — Both specialists independently re-verified the two items flagged in earlier review rounds (the transcript-guard HIGH and the ten-minute pending-create-window MEDIUM) against the current head content and source code; both hold up as addressed. No invalid/persnickety findings were filtered out during merge (0 dropped). Finding dispositions
Review diagnosticsNo tool calls failed or were denied, by either specialist or the orchestrator. Both specialists completed and wrote valid findings files without incident. Posted by the claude-review check — the review of this PR's diff at patch-id |
The number was justified by appeal to the contract's clause about keeping exited sessions listable, but the case the window serves is a session that finally appears alive, not one that has exited — the borrowed clause does not bound it. The number is probably fine; the reasoning was not, and a justification like that is what a later decision copies. Sized instead to the gap it has to span, which is not provisioning latency — that runs in minutes, which is why ten minutes suffices for the failure judgement — but discovery being unavailable, the condition the ledger exists for. A day covers a full working day of outage, and past that horizon a cloud session idle that long has had its environment reclaimed, so there is usually nothing left to adopt. Also records why a generous round number is adequate here rather than one worth deriving precisely: a session surfacing after the row is deleted is not lost. Discovery returns it and TBD adopts it as a session it did not create, which the contract already requires. Only the provenance link back to the create is lost. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015xU92u65ynrwqnugtegCab
|
Stopping here and handing this over. I said I would not keep re-running past a repeat, so I am not queueing another. Where the review actually stands. The last run that produced a verdict reviewed Rate. Across this branch: two failures at The most useful evidence for whoever fixes this is in the run that worked, at This can only be fixed on Separately, Generated by Claude Code |
Docs only. No Swift, no behavior change — see the handoff comment below for why, and for what a session on a Mac needs to pick up.
Lets TBD create, watch, steer, archive, and land Claude cloud sessions — Claude Code sessions running on Anthropic's hosted infrastructure, reachable today only from claude.ai, the mobile and Desktop apps, and
claude --cloudin a terminal. It rides the existing remote agent backend contract rather than introducing a parallel concept, so cloud sessions become remote sessions like any other: same mirror table, same sidebar, same health and auth machinery.The interesting part is that this is TBD's second provider, and a second implementation exposed places where a contract written against one implementation is unsound.
What's here
docs/specs/2026-08-07-claude-cloud-sessions-design.md— the design. Four pieces shipping together: contract v2, a provider compiled into the daemon, a land bridge from remote session to local worktree, and per-repo declaration of which remotes a repository runs on.docs/remote-provider-contract.md— revised to v2. States v2 as it stands, with no change notes, per the repo's no-revision-history rule.docs/remote-provider-contract-changes.md— migration guide for provider authors. Leads with the point that matters: a v1 provider is already a valid v2 provider, and the whole adoption step is declaringcontract_versions: [1, 2].One live bug this fixes, independent of the feature
v1's drift rule retires a session after two consecutive successful snapshots omit it.
RemoteSessionStore.applySnapshotincrementsmissingCountfor every row it didn't see, with no notion of whether the provider could see everything. That's correct only for a provider that enumerates its own inventory, and wrong for one that can enumerate only part of it — it tombstones live sessions. v2 addscompleteto the snapshot envelope; incomplete snapshots may adopt and update but never retire, and never count as refreshing freshness (which would have re-opened the mutation gate2026-08-01-remote-stale-snapshotclosed).Other contract v2 changes
archivedbecomes a third Session axis, orthogonal to liveness and attention, witharchive/unarchivecapabilities. Archived sessions must stay inlist— filtering them makes them look absent to the drift rule and denies the caller the inventory a revive flow browses. This gives remote sessions the lifecycle vocabulary local worktrees already have, including both revive modes.stopbecomes a capability and narrows to "terminate." v1 fused terminating compute with retiring from the inventory; one provider could do both in one call, so nothing forced them apart. A platform that reclaims idle sessions with no client-facing kill can retire but not terminate.transcript, distinct fromlog— agent transcript JSONL on stdout, continuation cursor as a JSON envelope on stderr. That split is the single exception to "stderr is diagnostic only" and exists so a truncated data stream stays recognizable as truncated.land, with the validation a caller must perform on provider-suppliedbranchandremote_urlbefore either reaches git.Security note worth reading
Part 4 defines a committed, repo-authored file (
.tbd-remotes.json) that TBD parses. An earlier draft called it inert data. It is not: repo-declared params are matched against the provider'screate_params, which includeprompt— so an unreviewed declaration could set the opening instruction of an agent holding repo write access, network egress, and the user's credential, and a single declared remote silently became the default location.Declarations now take effect only after a trust-on-first-use gate that displays every param value verbatim, with
promptshown in full. Approval is stored as the SHA-256 of the canonicalized declaration, so any edit re-prompts — approving once never blanket-approves what lands on the branch next month. The authoritative copy is the registered root checkout's, not the selected worktree's, so a branch under review can't redefine where sessions run merely by being selected.Flags
Nothing ships enabled.
claude_cloud_enabledis a new default-offconfigcolumn, and it's a second gate insideremoteGate()rather than a bypass — cloud requires both flags. It stays separate fromremote_backends_enabledbecause that flag was written to be deletable after soak, on the reasoning that the feature is inert without a registered provider file; a provider compiled into the daemon is never inert, so folding them would silently convert a disposable flag into a permanent one.Brainstorming
Ran per the repo convention, with a human answering. The spec was then reviewed by a fresh-context agent that verified its codebase claims against source and found three design faults — the prompt-injection channel above, an attach shim whose exit-code translation had no lawful input, and a ledger union that would have made "TBD launched it once" mean "it exists permanently." All three are fixed in
8b4e89b.🤖 Generated with Claude Code
https://claude.ai/code/session_015xU92u65ynrwqnugtegCab
Generated by Claude Code