Investigate the double subagent-stop path: agent prompts pipe into the CLI while the SubagentStop hook also fires #766
Labels
No labels
P0
P1
P2
P3
bug
category/code-change
category/cross-repo
category/eval-run
category/investigation
category/no-test-target
category/owner-ruling
category/prose-artifact
category/recurring-chore
category/structure
category/umbrella
crc-refactor
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
rule-proposal
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#766
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?
Problem
Ten agent prompts pipe a completion event into
os-sdlc-runner subagent-stopby hand. TheSubagentStophook also fires on the same event and callsDispatchCommands.subagent_stop_from_stdin. Two paths can record one completion. This can create double rows in the dispatch table and makes the true completion record unclear.Observed
The following agent prompts instruct the agent to run
printf ... | os-sdlc-runner subagent-stop:plugins/os-sdlc/hooks/subagent_stop.rbalso runs on the nativeSubagentStophook event and callsDispatchCommands.subagent_stop_from_stdin. An auto-memory note records that SubagentStart/Stop hooks fire natively, which suggests the prose-instructed pipe predates the working hook.Reproduce
n/a: investigation, not a reproducible bug.
Approach
VERDICT: CREATE (user-approved 2026-09-27)
Required work:
lib/os_sdlc/command/dispatch_commands*— the prose-instructed CLI pipe and the native hook call toDispatchCommands.subagent_stop_from_stdin.subagent-stopcall for the same dispatch.Acceptance criteria
SubagentStophook fire for the same agent run, when the dispatch table is inspected, then the report states whether one or two completion rows result.Owner sketch
n/a: investigation ticket, no new code.
Origin
DispatchCommandsdirectly, during session 9 CRC.subagent-stop(prompt pipe and native hook) ← DESIGN, prompt instructions written before hooks were confirmed to fire natively (see os-sdlc hooks fire natively memory note).Resolution
Done: Investigated. The two subagent-stop paths are not a race. The prompt-driven pipe (dispatch_id + YAML handoff, run by the agent after its gate) is the authoritative settlement; it records verdict, objections and next step. The native SubagentStop hook is keyed by session_id + agent_type and carries only last_assistant_message; in practice it fires after the prompt pipe and Lookup#late_hook_replay? makes it a silent no-op. Neither path writes a second row or overwrites state.
Evidence: lib/os_sdlc/command/completion_intake.rb:23-28 (guard: late_hook_replay? then dispatch_id_path? && completed? replay); lib/os_sdlc/query/completion_intake/lookup.rb:29-37; lib/os_sdlc/command/dispatch_commands.rb:56-66 (hook content = last_assistant_message); tests/pipeline/cli_subagent_stop_test.rb:179 and tests/hooks/subagent_stop_test.rb:53 pin both no-ops. Caller inventory 2026-09-27: 11 prompt lines pipe subagent-stop; brief has a live caller via presenter/hook_payload.rb:32 (Brief exceeds the hook cap message), so #768's uncalled set is four, not five.
Follow-ups: Hook safety-net test: dropped by the user as speculative hardening, none. #768 amended: brief is live, uncalled set is four, prompt lines and the hook-cap message rename in the same commit, stdout diff per command is the acceptance test. #767 unchanged: rewiring the hook to DispatchCommands changes nothing the pipeline observes.