Complete #140: cascade reasons fully skipped + AGENT sidecar (closes #151)#154
Conversation
Spec complianceAll 5 deliverables from #151 are present:
No missing deliverables. The spec's live smoke-test is left as an open checkbox in the PR description, which is appropriate for a real-session verification. Code-review findingsFinding 1 —
|
…ist, restore LOADOUT/AGENT preservation PR #150 left the cascade pattern from #140 unaddressed. The diagnostic logging it shipped surfaced the actual cascade signature on a real worker run: 144 unregister calls in ~25s, payload literally a single "y" character, reason=<unset> — phantom invocations upstream of Claude Code's documented SessionEnd hook (which always includes reason ∈ {clear, resume, logout, prompt_input_exit, other}). Each phantom call wiped .info, the sidecar mechanism from #150 wasn't reached, and the next re-seed produced LOADOUT=unknown / AGENT=claude. Three concrete changes in cmd_unregister + cmd_register + _ensure_session_file: 1. cmd_unregister's skip-list now also short-circuits on empty `.reason` in the JSON payload. Treats the cascade pattern the same as clear/resume — known non-real-exit events that must not wipe state. logout / prompt_input_exit / other still proceed normally. 2. AGENT sidecar parallel to the existing LOADOUT sidecar. Same hook-subshell unset problem: $TASK_FORCE_AGENT isn't inherited into Claude Code's hook env, so re-seeds defaulted to AGENT=claude regardless of whether the worker was kiro-*. Same write/read/sweep semantics; cmd_register writes both sidecars BEFORE the .info to preserve the kill-window invariant from #150 round-3. 3. cmd_unregister sweeps the new agent sidecar on real exit, leaves it alone on skip — symmetric to loadout sidecar behaviour. All 7 radio scripts updated (drift-checked region:body). 7 new bats cases pin the new contract end-to-end, including a 20× cascade-burst that asserts LOADOUT/AGENT/TAB_ID all survive. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
task-done invocations under `--force` typically have piped confirmation input (`echo y | task-done --force`); that stdin was inherited by the embedded `radio unregister` call. Pre-#151 it landed as garbage payload that jq couldn't parse — radio fell through to the proceed-with-delete path and cleanup worked. Post-#151 the same garbage payload routes to the new cascade-skip case (empty `.reason`), so the session file is left behind. Caught by tests/task_done.bats:assert_task_done_unregisters (7 loadout variants). Fix: redirect the unregister's stdin from /dev/null in task-done so the hook payload parsing sees an empty stdin and proceeds normally. All 7 task-done scripts updated (drift-checked: task-done-std/confirm-and-cleanup and task-done-local/confirm-and-cleanup byte-identical within group). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3d571ba to
9ba5669
Compare
Summary
Completes the #140 fix that PR #150 left half-done. The diagnostic logging shipped in #150 surfaced the actual cascade signature on a long-running worker: 144 unregister calls in ~25s, payload literally a single
ycharacter, reason=<unset>— phantom invocations from somewhere upstream of Claude Code's documentedSessionEndhook (which always includesreason ∈ {clear, resume, logout, prompt_input_exit, other}). Each phantom call wiped~/.task-force/radio/sessions/<role>.info; the sidecar mechanism from #150 wasn't reached on this code path; the next re-seed producedLOADOUT=unknown/AGENT=claudeandcmd_send's tab-id-based wake-up died for the rest of the worker's life.Diagnostic findings
From
~/.task-force/radio/log:Frequency table of
payload=values across the log: 144 ×y, 0 of anything else. Skip-list hits (clear/resume): 9. So 100% of unaccounted-for unregister calls are this single signature.Changes
Three concrete changes in
cmd_unregister+cmd_register+_ensure_session_file:cmd_unregister's skip-list now also short-circuits on empty.reasonin the JSON payload. Treats the cascade pattern the same asclear/resume.logout/prompt_input_exit/otherstill proceed normally.AGENT sidecar parallel to the existing LOADOUT sidecar. Same hook-subshell unset problem:
\$TASK_FORCE_AGENTisn't inherited into Claude Code's hook env, so re-seeds defaulted toAGENT=clauderegardless of whether the worker was kiro-*. Same write/read/sweep semantics;cmd_registerwrites both sidecars BEFORE the.info(preserves the kill-window invariant from Radio session corruption: unregister cascade → re-seed loses TAB_ID + LOADOUT #150 round-3).cmd_unregistersweeps the new agent sidecar on real exit, leaves it alone on skip — symmetric to loadout sidecar behaviour.All 7 radio scripts updated (drift-checked
region:body). 7 new bats cases pin the new contract end-to-end, including a 20× cascade-burst that assertsLOADOUT/AGENT/TAB_IDall survive.CHANGELOG
Single combined
[Unreleased]Fixed entry covering #140 + #150 + #151 (the PR-#150-only entry never landed). Frames the three concrete fixes (Fix B, Fix C, cascade skip) as a single bug cluster — that's how it'll read to anyone debugging similar symptoms on v0.3.0.Test plan
./run_tests.sh radio_lifecycle— 38 lifecycle tests pass, including 7 new Complete #140: analyze cascade-payload logs, expand SessionEnd skip-list, restore LOADOUT/AGENT preservation #151 cases./run_tests.sh— full suite (~785 tests) passes, exit 0./tools/check-drift.sh— all 21 sentinel-region groups byte-identicalshellcheck claude-gh/bin/radio— clean--autoworker: runtask-work --auto …long enough to trigger the cascade (PM workflow + several subagent ends), thencat ~/.task-force/radio/sessions/worker-*.infoand confirmTAB_IDnon-empty,LOADOUTcorrect,AGENTcorrect,tail ~/.task-force/radio/logshows no_ensure_session: re-seededlines (PM verification step from the issue).Closes #151.
🤖 Generated with Claude Code