You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Let sub-agent task checkouts be renamed and deleted safely while several Xum processes share one XUM_ROOT. This is the missing prerequisite for the paused checkout-preparation stack (#4364 and its stack). Maintainer requirements: renames must work, and sub-agents must be cleaned up on archive and delete.
Why it is needed
The preparation stack refused every structural change to a task checkout: rename, remove, archive-delete/snapshot, restore. Two problems motivated that:
Late clean-up hits a successor. One process rotated a task's attempt in config after another process's owner check but before its deleteWorkspace. The late delete destroyed the successor's checkout and session.
Work may still be running elsewhere. Local "finished" state cannot prove that another cooperating process has no admitted work, tool child or MCP server still running in the checkout.
Required sequence
Close new admission for the footprint.
Establish quiescence, or return a truthful "busy" refusal. Never wait while holding a lock that completion or use-release needs.
Revalidate exclusively and mutate.
Reopen admission against the resulting state.
Proposed shape (design pass, 2026-09-25; not implemented)
Use records per footprint key (canonical checkout realpath), under XUM_ROOT/checkout-uses/<key>/: {useId, workspaceId, attemptId?, kind, owner: process identity, groups: [{pgid, leader birth}]}.
Created before each effect, deleted when it ends.
Written at: turn admission to release, LocalBaseRuntime.exec process groups, MCP server lifetime, init hooks, terminal PTYs, devcontainer execs.
Rename and preparation proof: capture identity and nonce before git worktree move. Re-capture after it: dev/ino, the .git pointer and the nonce must be unchanged, with only the path changed. Publish a re-signed proof in the same config edit as the name/path change. This amends the contract's "proof is immutable" rule for that one transition.
Crash recovery:
Delete: a pendingRemoval {removalId, materializationId, owner} marker set by compare-and-swap before deleting.
Rename: an intent journal {from, to, materializationId, phase, owner}. Roll back if only the old path exists; roll forward if only the new path exists with a matching nonce; otherwise refuse.
Estimate: about 1.2k production and 2–2.5k test lines, including multi-process SIGKILL harnesses. A smaller variant (about 600 lines, no journal) leaves the gate after an interrupted operation and refuses with an index.lock-style recovery message. That brings back a lockout, which the maintainer rejected.
Open design gaps (must be solved before implementation)
Spawn-to-record crash window: a child can start before its pgid is recorded. It needs a pre-exec handshake (for example sh -c 'read _ && exec …' released only after the record is durable).
Lifetimes that can't be proven: container-side processes of devcontainer execs, and escaped daemons (setsid, double fork). These must be declared out of contract explicitly, or covered some other way.
Linearization proof for admission versus mutation across publication, incomplete records, aliases and rename.
Clean-up versus successor creation: every relevant writer must take part in exclusion through the destructive effect.
Idle foreign MCP servers: they would make an idle workspace "busy". The owning process could stop its own servers first. Today stopServers runs after the checkout is deleted (workspaceService.ts remove path).
Delivery
Registry, gate, busy refusal and attempt-bound removal are useful on main on their own (see the linked issue on main's rename/delete gaps). They could land first.
The proof re-sign has to land with the preparation stack.
The stack then swaps its blanket refusal (workspaceStructuralMutationGuard.ts and its call sites) for the gate.
Integrated local stack on current main, all gates green: branch park/checkout-preparation-integration (f52e6a1). It includes behavior tests for today's refusals and a killed-coordinator writer test.
Summary
Let sub-agent task checkouts be renamed and deleted safely while several Xum processes share one
XUM_ROOT. This is the missing prerequisite for the paused checkout-preparation stack (#4364 and its stack). Maintainer requirements: renames must work, and sub-agents must be cleaned up on archive and delete.Why it is needed
The preparation stack refused every structural change to a task checkout: rename, remove, archive-delete/snapshot, restore. Two problems motivated that:
deleteWorkspace. The late delete destroyed the successor's checkout and session.Required sequence
Proposed shape (design pass, 2026-09-25; not implemented)
XUM_ROOT/checkout-uses/<key>/:{useId, workspaceId, attemptId?, kind, owner: process identity, groups: [{pgid, leader birth}]}.LocalBaseRuntime.execprocess groups, MCP server lifetime, init hooks, terminal PTYs, devcontainer execs.src/node/utils/concurrency/processLiveness.ts(🤖 fix: never take a cross-process lock from a live holder #4461, 🤖 fix: never break the config file lock of a live holder #4464). A process group is dead whenkill(-pgid, 0)returns ESRCH.gate.json): write the gate, then scan uses. Admission writes its use, then reads the gate, so one side always sees the other.taskAttemptIdandmaterializationIdcaptured at decision time. Check the nonce before deleting. 🤖 feat(tasks): fence sub-agent attempts before durable workflow recovery #4308's auto-clean-up of finished leaves must bindexpectedAttemptId.git worktree move. Re-capture after it: dev/ino, the.gitpointer and the nonce must be unchanged, with only the path changed. Publish a re-signed proof in the same config edit as the name/path change. This amends the contract's "proof is immutable" rule for that one transition.pendingRemoval {removalId, materializationId, owner}marker set by compare-and-swap before deleting.{from, to, materializationId, phase, owner}. Roll back if only the old path exists; roll forward if only the new path exists with a matching nonce; otherwise refuse.Estimate: about 1.2k production and 2–2.5k test lines, including multi-process SIGKILL harnesses. A smaller variant (about 600 lines, no journal) leaves the gate after an interrupted operation and refuses with an
index.lock-style recovery message. That brings back a lockout, which the maintainer rejected.Open design gaps (must be solved before implementation)
sh -c 'read _ && exec …'released only after the record is durable).stopServersruns after the checkout is deleted (workspaceService.tsremove path).Delivery
mainon their own (see the linked issue onmain's rename/delete gaps). They could land first.workspaceStructuralMutationGuard.tsand its call sites) for the gate.References
main, all gates green: branchpark/checkout-preparation-integration(f52e6a1). It includes behavior tests for today's refusals and a killed-coordinator writer test.XUM_ROOTruns in one PID domain.Generated with
xum• Model:anthropic:claude-opus-5-5• Thinking:high