SubagentStart records a phantom dispatch when a subagent resumes after its own background work #773

Open
opened 2026-09-27 14:59:19 +00:00 by jared · 3 comments
Owner

Problem

The SubagentStart hook records a new dispatch each time it fires.
A subagent that stops with its own background work still running resumes when that work completes, and Claude Code fires SubagentStart again for the resume.
The Runner then holds a second open dispatch for an agent that already stopped and settled.
os-sdlc-next returns await_completion for 900 seconds until StallWatch marks the phantom dispatch stalled, and the real pipeline waits that long.

Observed

On 2026-09-27, implementation 50 (#728) dispatched programmer 515, which stopped at 10:52:21 with verdict pass and background work still running.
Contract-auditor 518 started at 10:53:50.
At 10:55:18 a programmer dispatch 519 appeared with no brief, no handoff, and no gate steps; its agents.agent_id is the same agent ID as dispatch 515.
After 518 settled with verdict fail at 10:56:31, the Runner did not route it.

os-sdlc-next 17688436-99ef-4a59-9eec-74e4f87e9c1f
{"type":"await_completion","session_id":"17688436-99ef-4a59-9eec-74e4f87e9c1f","implementation_id":50}

Rows from .sdlc/pipeline.db:

dispatches 519: implementation_id 50, received_handoff_id nil, created_handoff_id nil, verdict nil, created_at 2026-09-27 10:55:18
agents 519: agent_id a16e7adb929696d3a, agent_type programmer
agents 515: agent_id a16e7adb929696d3a, agent_type programmer

Reproduce

1. Open an implementation and dispatch a programmer that launches a Bash command with run_in_background and stops before it completes.
2. Dispatch the next node the Runner names.
3. Let the background command finish; the programmer resumes.
4. os-sdlc-next SESSION_ID

Observed: a second programmer dispatch with the same agent ID and await_completion for 900 seconds. Expected: no new dispatch for an agent ID that already settled a dispatch in this implementation.

Approach

The SubagentStart handler already receives the agent ID.
Before it creates a dispatch, it looks up an existing agents row with the same agent_id in the same implementation that has a completed dispatch.
When it finds one, it records nothing and exits 0.

Required work:

  1. In the SubagentStart command, skip dispatch creation when the payload's agent ID already belongs to a completed dispatch of the open implementation.

Rejected: shortening StallWatch::STALL_AFTER_SECONDS — it treats the symptom, and a live slow agent would then stall falsely.

Acceptance criteria

  1. Given a programmer dispatch that has completed, when SubagentStart fires again with the same agent ID, then the Runner records no new dispatch and os-sdlc-next routes the last completed dispatch.
  2. Given a fresh agent ID, when SubagentStart fires, then the Runner records a dispatch as before.

Owner sketch

class OsSdlc::StartSubagent
  def resumed_agent?(agent_id)
  end
end

Origin

  • Trigger: os-sdlc-next 17688436-99ef-4a59-9eec-74e4f87e9c1f during /os-sdlc:implement 728, 2026-09-27
  • Improvised this session: waited for StallWatch (900 s) to mark dispatch 519 stalled instead of editing the database
  • Chain: pipeline stuck in await_completion ← open dispatch 519 with no brief ← SubagentStart fired on subagent resume ← DESIGN (plugins/os-sdlc/lib/os_sdlc/command/start_subagent.rb creates a dispatch on every SubagentStart; #766 closed 2026-09-27 ruled the prompt pipe authoritative for stop, but start has no resume guard)
  • Root candidate: this ticket
  • Where: plugins/os-sdlc/lib/os_sdlc/command/start_subagent.rb
  • Session: 17688436-99ef-4a59-9eec-74e4f87e9c1f
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/17688436-99ef-4a59-9eec-74e4f87e9c1f.jsonl

VERDICT: CREATE
REASON: Complete-process, database-evidenced defect (dispatch 519 phantom, 15-min stall) in a real pipeline run, with root traced to DESIGN in start_subagent.rb, distinct from #766 stop-side scope; no upstream mechanism already covers a resume guard.

## Problem The SubagentStart hook records a new dispatch each time it fires. A subagent that stops with its own background work still running resumes when that work completes, and Claude Code fires SubagentStart again for the resume. The Runner then holds a second open dispatch for an agent that already stopped and settled. `os-sdlc-next` returns `await_completion` for 900 seconds until StallWatch marks the phantom dispatch stalled, and the real pipeline waits that long. ## Observed On 2026-09-27, implementation 50 (#728) dispatched programmer 515, which stopped at 10:52:21 with verdict pass and background work still running. Contract-auditor 518 started at 10:53:50. At 10:55:18 a programmer dispatch 519 appeared with no brief, no handoff, and no gate steps; its `agents.agent_id` is the same agent ID as dispatch 515. After 518 settled with verdict fail at 10:56:31, the Runner did not route it. ``` os-sdlc-next 17688436-99ef-4a59-9eec-74e4f87e9c1f ``` ``` {"type":"await_completion","session_id":"17688436-99ef-4a59-9eec-74e4f87e9c1f","implementation_id":50} ``` Rows from `.sdlc/pipeline.db`: ``` dispatches 519: implementation_id 50, received_handoff_id nil, created_handoff_id nil, verdict nil, created_at 2026-09-27 10:55:18 agents 519: agent_id a16e7adb929696d3a, agent_type programmer agents 515: agent_id a16e7adb929696d3a, agent_type programmer ``` ## Reproduce ``` 1. Open an implementation and dispatch a programmer that launches a Bash command with run_in_background and stops before it completes. 2. Dispatch the next node the Runner names. 3. Let the background command finish; the programmer resumes. 4. os-sdlc-next SESSION_ID ``` Observed: a second programmer dispatch with the same agent ID and `await_completion` for 900 seconds. Expected: no new dispatch for an agent ID that already settled a dispatch in this implementation. ## Approach The SubagentStart handler already receives the agent ID. Before it creates a dispatch, it looks up an existing `agents` row with the same `agent_id` in the same implementation that has a completed dispatch. When it finds one, it records nothing and exits 0. Required work: 1. In the SubagentStart command, skip dispatch creation when the payload's agent ID already belongs to a completed dispatch of the open implementation. Rejected: shortening `StallWatch::STALL_AFTER_SECONDS` — it treats the symptom, and a live slow agent would then stall falsely. ## Acceptance criteria 1. Given a programmer dispatch that has completed, when SubagentStart fires again with the same agent ID, then the Runner records no new dispatch and `os-sdlc-next` routes the last completed dispatch. 2. Given a fresh agent ID, when SubagentStart fires, then the Runner records a dispatch as before. ## Owner sketch ```ruby class OsSdlc::StartSubagent def resumed_agent?(agent_id) end end ``` ## Origin - Trigger: `os-sdlc-next 17688436-99ef-4a59-9eec-74e4f87e9c1f` during /os-sdlc:implement 728, 2026-09-27 - Improvised this session: waited for StallWatch (900 s) to mark dispatch 519 stalled instead of editing the database - Chain: pipeline stuck in `await_completion` ← open dispatch 519 with no brief ← SubagentStart fired on subagent resume ← DESIGN (`plugins/os-sdlc/lib/os_sdlc/command/start_subagent.rb` creates a dispatch on every SubagentStart; #766 closed 2026-09-27 ruled the prompt pipe authoritative for stop, but start has no resume guard) - Root candidate: this ticket - Where: plugins/os-sdlc/lib/os_sdlc/command/start_subagent.rb - Session: 17688436-99ef-4a59-9eec-74e4f87e9c1f - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/17688436-99ef-4a59-9eec-74e4f87e9c1f.jsonl VERDICT: CREATE REASON: Complete-process, database-evidenced defect (dispatch 519 phantom, 15-min stall) in a real pipeline run, with root traced to DESIGN in start_subagent.rb, distinct from #766 stop-side scope; no upstream mechanism already covers a resume guard.
Author
Owner

Occurrence: Keep a dispatch from binding to a finished agent
Seen: os-sdlc Runner dispatch binding, SubagentStart/stop handling, os-sdlc-next
Run: implementation 250 (#1616), session 9d8c2e15-00c1-4dec-b177-e749fa05ec67
Adds: A double notification from the test-writer bound dispatch 2384 to finished agent a5044b6c89ae3d206. os-sdlc-next returned await_completion with no end, the stalled result never fired, and the run needed a hand-finish. In the same run, test-reviewer dispatch 2383 failed new tests for being green in a red round, although they were correct regression pins. The map has no path for that case.

Occurrence: Keep a dispatch from binding to a finished agent Seen: os-sdlc Runner dispatch binding, SubagentStart/stop handling, os-sdlc-next Run: implementation 250 (#1616), session 9d8c2e15-00c1-4dec-b177-e749fa05ec67 Adds: A double notification from the test-writer bound dispatch 2384 to finished agent a5044b6c89ae3d206. os-sdlc-next returned await_completion with no end, the stalled result never fired, and the run needed a hand-finish. In the same run, test-reviewer dispatch 2383 failed new tests for being green in a red round, although they were correct regression pins. The map has no path for that case.
Author
Owner

Occurrence: SubagentStart hook records a resumed non-pipeline agent as a dispatch of the open implementation
Seen: plugins/os-sdlc/lib/os_sdlc/dispatch/admission.rb (SubagentStart admission)
Run: Session 87bc217f-61e2-4d4d-8470-9f3c8fd1ef43, implementation 267 (#1642), dispatch 2615
Adds: the resumed agent was an os-sdlc:blast-radius agent launched before implementation-open (not a map agent, never calls os-sdlc-subagent-stop). After dispatch 2614 (test-reviewer pass) the hook recorded the resume as dispatch 2615 agent_type blast-radius; os-sdlc-next returned await_completion until StallWatch (900 s). The guard must also refuse an agent type that is not the current node's agent.

Occurrence: SubagentStart hook records a resumed non-pipeline agent as a dispatch of the open implementation Seen: plugins/os-sdlc/lib/os_sdlc/dispatch/admission.rb (SubagentStart admission) Run: Session 87bc217f-61e2-4d4d-8470-9f3c8fd1ef43, implementation 267 (#1642), dispatch 2615 Adds: the resumed agent was an os-sdlc:blast-radius agent launched before implementation-open (not a map agent, never calls os-sdlc-subagent-stop). After dispatch 2614 (test-reviewer pass) the hook recorded the resume as dispatch 2615 agent_type blast-radius; os-sdlc-next returned await_completion until StallWatch (900 s). The guard must also refuse an agent type that is not the current node's agent.
Author
Owner

Occurrence addendum (implementation 267): the stall directive re-dispatched test-reviewer although dispatch 2614 had already passed it; the re-dispatch (2616) returned verdict error and the run ended implementation_failed. So the phantom dispatch does not only cost 900 s: it can fail the run.

Occurrence addendum (implementation 267): the stall directive re-dispatched test-reviewer although dispatch 2614 had already passed it; the re-dispatch (2616) returned verdict error and the run ended implementation_failed. So the phantom dispatch does not only cost 900 s: it can fail the run.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#773
No description provided.