Dispatch settlement unreliable: SubagentStop hook never fires #496
Labels
No labels
P0
P1
P2
P3
bug
create
delete
enhancement
filed-by/agent
filed-by/user
frozen
lint-rule
needs-info
needs-triage
next
plugin/cc-architect
plugin/os
plugin/os-adr
plugin/os-aidd-lint
plugin/os-backlog
plugin/os-context
plugin/os-doc-hygiene
plugin/os-sdlc
plugin/os-vault
project/cc-os
ready-for-agent
ready-for-human
recurring
review
update
waiting
wayfinder:grilling
wayfinder:map
wayfinder:map
wayfinder:research
wayfinder:task
wayfinder:task
wontfix
worklist/deviations
worklist/lint-rule
worklist/new-implement-build
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jared/cc-os#496
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Recorded 2026-08-23 during the #491 worktree session: async dispatches stalled at await_completion with completed_at nil because Claude Code's SubagentStop hook never fired (upstream anthropics/claude-code#27755 — reconnected subagents never fully stop). Needed: agent-initiated settlement keyed by dispatch id, idempotent settle (late hook fire tolerated), and a runner-side settlement fallback for agents that crash before settling. Note: this issue was created retroactively; it must carry index #495 to match references in ADR-0144, commit bbc5162, and docs/implementation-status/os-sdlc.md.
Shipped in the amended settlement commit (ADR-0144, was referenced as #495 before this index turned out to be a ghost): agents now settle their own dispatches. subagent-stop gained a dispatch-id-keyed path (session_id/agent_type resolved via relations), the settlement reply carries NextStep's instruction + dispatch_id for verbatim relay to the orchestrator, transcript path is reconstructed via AgentTranscriptPath, and a late SubagentStop hook fire on an already-settled dispatch is a quiet no-op. All mapped agent files carry the settle step; Bash granted where missing. Still open: runner-side settlement fallback for agents that crash before settling, and live verification on a real dispatch.
Approved design for the crash fallback (2026-08-24, session retro of #491). Three layers, no retries, every detection routes the map's error edge — fail loudly.
Layer 1 — SubagentStop hook detects unsettled stops. In CompletionIntake, when the hook entry path finds the dispatch still pending and carries no handoff content, that is the failure signal: stamp the dispatch failed (reason stopped_without_settlement), record an error verdict ("agent stopped without running subagent-stop"), and run NextStep so the node's error edge routes or the implementation fails. late_hook_replay? already makes the self-settled case a no-op.
Layer 2 — deadline reaper for hard crashes (process/session death, no hook). Add dispatches.deadline_at, stamped at dispatch time from map config (defaults.deadline_minutes with per-node deadline_minutes override, e.g. programmer: 45). A Reaper class marks overdue pending dispatches failed (deadline_exceeded) with an error verdict. Run the reaper inside every runner entry point that reads state (next_step, implementation-status, subagent-start). No daemon — lazy freshness. A false reap on a slow node is a loud error edge, the correct side to err on.
Layer 3 —
os-sdlc-runner status(with --json): one table over dispatch rows showing per-implementation node, dispatch id, age, deadline, and state (running / FAILED reason / complete). This is how an orchestrator of parallel implementations observes liveness without polling agents.Acceptance criteria:
os-sdlc-runner statuslists each implementation's latest dispatch with node, age, deadline, and state;--jsonemits the same machine-readably.Work starting on branch worktree-ticket-496 via the os-sdlc pipeline (ADR-0145 records the design; see the design comment above for acceptance criteria).
Crash-fallback settlement shipped (commit
80713f3, branch worktree-ticket-496). Blank/empty content on subagent-stop now settles the dispatch as verdict error, step stopped_without_settlement, and still relays next_step. Live-verified by settling crashed programmer dispatch 37 from the interrupted 2026-08-24 run (implementation 5 recorded as implementation-failed; code product was complete and green, finished manually). Suite 1054 runs / 0 failures; rubocop clean. Still open on this ticket: the true-crash case where subagent-stop is never called at all, and the test-writer relay-drop pattern (0-for-2 in the live run).