Symptom
spawnNode can create a child Session and then fail while persisting NodeStarted. The node is terminalized through the outer nodeFailed fallback, but the newly created child Session is neither referenced by the durable DAG row nor cancelled.
Reproduction
Baseline: dev@3642796de
cd packages/opencode
bun test test/dag/spawn-completion.test.ts --test-name-pattern "cancels the child session when nodeStarted fails after session creation"
Observed deterministic red result:
promptCalled: false
nodeFailed reason contains "nodeStarted write failed"
cancelCalled: false
Root cause
spawnNode assigns childSessionID immediately after sessions.create. Its cleanup cancels that Session for a transition rejection and for fiber interruption, but the generic Effect.catchCause path only publishes nodeFailed. A non-transition failure from dag.nodeStarted therefore leaves an untracked child Session.
Expected contract
Any execution exit before a durable NodeStarted association must cancel the materialized child Session. Once NodeStarted succeeds, normal runtime ownership and terminal handlers remain responsible for cancellation.
Acceptance criteria
- Retain the public spawn-seam red test.
- Cancel the child Session on non-interrupt failure after creation and before/while node settlement.
- Preserve the existing transition-rejection path and avoid double terminal events.
- Keep semaphore and deadline-watcher cleanup unchanged.
- Run the complete spawn test file, OpenCode DAG suite, and package typecheck.
Symptom
spawnNodecan create a child Session and then fail while persistingNodeStarted. The node is terminalized through the outernodeFailedfallback, but the newly created child Session is neither referenced by the durable DAG row nor cancelled.Reproduction
Baseline:
dev@3642796deObserved deterministic red result:
Root cause
spawnNodeassignschildSessionIDimmediately aftersessions.create. Its cleanup cancels that Session for a transition rejection and for fiber interruption, but the genericEffect.catchCausepath only publishesnodeFailed. A non-transition failure fromdag.nodeStartedtherefore leaves an untracked child Session.Expected contract
Any execution exit before a durable
NodeStartedassociation must cancel the materialized child Session. OnceNodeStartedsucceeds, normal runtime ownership and terminal handlers remain responsible for cancellation.Acceptance criteria