Repository navigation
fix(cloud-agent-next): decide worktree deletion exclusivity from a local reference index - #6238
Conversation
…cal reference index Worktree deletion failed for accounts with many root sessions that have no worktree. `SandboxControl.fenceAndCheckWorktreeExclusivity` resolved sandbox exclusivity by issuing one serial `getRuntimeLocation` DO RPC per owner the ledger could not locate, inside a 30s `withTimeout`. With ~522 such owners that is ~30s, so the deletion ended at `runtime_cleanup` with "Worktree ownership lookup timed out" and, because a plain timeout carries no retryable flag, every retry repeated it. The SandboxControl DO now owns a durable per-session reference index for its sandbox: - `attachSession` records a reference atomically with its route; - `detachSession` keeps the removed route's reference as a tombstone, so a session that detaches keeps blocking destruction; - session delete, worktree release and sandbox destroy clear entries; - `fenceAndCheckWorktreeExclusivity` decides from `references ∪ routes` through one predicate, with no `withTimeout` and no unbounded fan-out. References that predate the index are covered by one bounded, concurrent reconciliation per sandbox, gated by a persisted marker. A ledger `shared` verdict never certifies completeness, malformed state fails closed, and budget or history exhaustion returns an incomplete verdict instead of throwing.
The repo-level format check runs oxfmt over the whole tree, while the package script covers only src/scripts/test/postgres, so this file was not formatted before the first push. No semantic change.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryIncremental review of the follow-up commit ( Verification notes specific to this increment:
Files Reviewed (3 files)
Previous Review Summary (commit e9d0d77)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit e9d0d77)Status: No Issues Found | Recommendation: Merge Executive SummaryReviewed the durable session-reference index, the bounded Verification notes:
Files Reviewed (13 files)
Reviewed by deepseek-v4.1-flash · Input: 0 · Output: 0 · Cached: 0 Review guidance: REVIEW.md from base branch |
…e control-plane `reconcileSandboxReferences` issued one `getRuntimeLocation` DO RPC per owner the ledger could not locate. For the account that hit the ownership timeout, ~522 of ~540 roots are legacy `agent_*` sessions with no worktree, and a legacy session cannot hold a control-plane sandbox id: isolated ids hash the session id, shared ids derive from a plane-versioned route key, and the only cross-session copy of a workspace sandbox stays inside one worktree. Those owners are no longer consulted through their session DO. They still block when the ledger already recorded an allocation naming the target sandbox and provider, and every owner that could be control-plane - all worktree owners and all `workspace_*` owners - keeps its existing lookup, ordering and `unavailable` behaviour unchanged. Accepted trade, recorded deliberately: the legacy locator channel is withdrawn. A legacy session that referenced the target sandbox without a matching recorded allocation no longer blocks deletion, so that workspace can be destroyed. Current normal routing paths do not create that cross-plane assignment, but historical or atypical persisted assignments are not proven absent. Existing coverage that asserted the withdrawn channel is re-pointed at control-plane owners; the recorded-allocation blocker keeps its own regression test.
Problem
Deleting a Cloud Agent worktree failed every time for an account with many root sessions:
The message is a catch-all. Axiom production logs show the real cause — both attempts ended at stage
runtime_cleanupafter 30127 ms / 30126 ms:Root cause (verified from logs and read-only SQL)
SandboxControl.fenceAndCheckWorktreeExclusivityresolved sandbox exclusivity by callingSESSION_INGEST.canDestroyCloudAgentWorktreeSandboxand then issuing one serialgetRuntimeLocation()DO RPC per owner the ledger could not locate, with no per-call timeout, insidewithTimeout(..., DEADLINE_MS.stopAttempt)= 30 s.Measured for the affected user: 540 root sessions, 522 of them with no worktree, while the DB side of the check is ~44 ms. 522 × ~57 ms ≈ 30 s, so the timeout always fired and
withRetrynever retried it (a plain timeout carries noretryableflag). The walk is unbounded and account-wide.Refuted: a missing index made the ownership SQL slow. The plans are already 44 ms — the cost was RPC fan-out.
Change
The
SandboxControlDO for a sandbox now owns a durable per-session reference index for it, and exclusivity is decided from local state:sandbox-control/session-references.ts— the index domain and the singlehasForeignReferencepredicate answering "is this evidence foreign to the target worktree?".durable-state.ts— thesession_referenceskey, a strict schema (512 entries / 96 KiB, per-field maxima), absent → empty, malformed → throws, erased with the sandbox record.SandboxControl.ts—attachSessionrecords a reference atomically with its route;detachSessionkeeps the removed route as a tombstone (so a detached session still blocks destruction); the worktree prune clears detached entries;fenceAndCheckWorktreeExclusivitydecides fromreferences ∪ routes, with nowithTimeout; newforgetSessionReferenceRPC.sandbox-control/worktree-ownership.ts— the serial walk becomesreconcileSandboxReferences: one 20 s budget over the ledger RPC plus a concurrency-8 locator pool, preserving owner-ordered verdicts, returning an incomplete verdict instead of throwing.sandbox-session/control-rpc.ts,sandbox-session/SandboxSession.ts— deadline-scoped best-effort forget on session delete; failure does not block the user-visible deletion.Pre-deployment references that cannot be derived from the index are covered by one bounded reconciliation per sandbox, gated by a persisted marker. Design invariants that keep the verdict safe:
sharedverdict never certifies completeness (it carries no evidence, so it blocks only the current decision);unavailablehistory stays recoverable, and capacity overflow fails closed.Verification
Focused gates on the merged tree:
pnpm -C services/cloud-agent-next run typechecktest/integration/worktree-deletion.test.ts(realworkerd/Miniflare DO SQLite)run lintrun format:checkEnd-to-end, against the real Durable Object under
workerd: the diagnostic showsdecision: exclusivewithevidence: runtime_reconciliation/ledger,session_locator0 andtimed out0; and all three directions hold — foreign session attached ⇒ no stop, foreign session detached (tombstone) ⇒ no stop, no foreign reference ⇒ destroyed.Worktree ownership lookup timed outandresolveSandboxExclusivityno longer exist anywhere in the repo.Not verified against a running production system: the live
deleteWorktreesuccess, the real Postgres ledger and real container destruction. Deleting a real worktree is a destructive production action and was not run; the production-scale timing (~522 owners, ~57 ms) is projected from the measured latency, not re-measured.Accepted trade-offs and follow-ups
unavailable⇒ block (fail safe, but that sandbox is not destroyable by worktree deletion until evidence reappears).worktree-deletion.ts:221-232, which still hides the real cause for other errors.Follow-up commit: do not walk owners that cannot be control-plane
The first commit bounds the walk but still visits every owner the ledger cannot locate — for the affected account, ~522 of ~540 roots are legacy
agent_*sessions with no worktree. The second commit stops consulting those through their session DO, because a legacy session cannot hold a control-plane sandbox id: isolated ids hash the session id, shared ids derive from a plane-versioned route key (shared-v3vsshared-control-v1), the failover override only appends a fixed suffix to the same route key, and the only cross-session copy of a workspace sandbox stays inside one worktree.Those owners still block when the ledger has already recorded an allocation naming the target sandbox and provider. Every owner that could be control-plane — all worktree owners and all
workspace_*owners — keeps its previous lookup, owner-ordered verdict andunavailablebehaviour unchanged.Accepted trade, recorded deliberately: the legacy locator channel is withdrawn. A legacy session that referenced the target sandbox without a matching recorded allocation no longer blocks, so that workspace could be destroyed. Current normal routing paths do not create that cross-plane assignment, and persisted assignments are not universally provenance-validated, so historical or atypical records are not proven absent. Existing tests that asserted the withdrawn channel are re-pointed at control-plane owners, and the recorded-allocation blocker keeps its own regression test.