docs: 'When radio misbehaves' runbook — symptom→cause table, log diagnostics, post-#187/188/189/190 behavior - #204
Merged
Conversation
…nostics, post-#187/188/189/190 behavior Radio's docs described the happy path well and the failure path barely: there was no place a user or an agent could look to answer "radio isn't working — now what?". Adds a consolidated runbook to the README's radio section and to all seven workflow templates (plus this repo's dogfooded copy). Covers the five symptoms people actually report: a ping that went nowhere, `radio check` sitting unsubmitted in a prompt box (#189), a role that keeps vanishing from sessions/ (flapping, and how to confirm #187/#198's guard is the binary actually running), `radio unregister` doing nothing (expected since #198), and a role idling on a message in its own inbox (fixed in #197, with BLOCKED_IDS= named). Documents ~/.task-force/radio/log as a diagnostic for the first time — delivery rate, wake failures bucketed by reason, wipes vs. refusals, the #198 stdin shape, the #188 tab_id_src= re-seed fields. Every grep in the runbook was validated against a real 6,300-line production log. Two honesty gaps the spec named are closed: undelivered mail is never dropped (written to the inbox before any wake; gc never touches inbox/ and refuses to reclaim a mailbox holding unread mail) and there is **no** dead-letter queue — the spec assumed #188 shipped one, it did not, so the runbook says so rather than sending readers after a directory radio never creates. And delivery is not symmetric: the claude copy names the three backstops it may promise, the kiro copy names none and points at the agent's own poll (#190). One asymmetry surfaced while writing and is now documented: kiro's task-work parses no --auto flag, so AUTO_MODE is never set and TASK_FORCE_AUTO_SUBMIT is never injected for a kiro worker — its wakes always end in LF and always need the keypress. tests/radio_runbook.bats (12 tests) pins block identity within each loadout group, #177 neutrality, the claude/kiro asymmetry, and — load-bearing — that every log string the runbook greps for is a literal substring of bin/radio, so a rename there fails the suite instead of quietly turning the runbook into a page of commands that match nothing. Mutation-checked. Also replaces the `--owner ignored` sentinel in the two task-init overwrite tests: it scans the whole rendered doc, so an ordinary English word false-fails as soon as the template prose uses it (which this runbook does). Docs only — bin/radio is untouched. Closes #191 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzeuHXdqABg52t7ug7UDje
…t just a stale binary Rebased onto #205, which proves the diagnosis in the flapping row was incomplete. `--manual` short-circuits the `if [[ "$manual" != true ]]` block that emits BOTH the `skipping` and `proceeding` lines, so a manual wipe writes `unregister role=` and nothing else. Anything calling it in a loop therefore inflates that counter on its own — exactly what #205 found, where an unisolated test suite unregistered the developer's live role 57× per run. The original wording attributed a role=/proceeding gap solely to an old `radio` binary on PATH. That would have misdiagnosed the one real incident we have data for: I hit this exact gap while researching the runbook (1368 wipes in a day against 3 `proceeding` lines) and wrongly concluded stale binary. #205 is the actual explanation. All nine docs now name the --manual cause first and keep the stale-binary one second. New test 8 pins it, asserting both causes appear in every copy and that bin/radio really does gate both log lines behind the manual check. Verified against #205's harness: full suite 1013/1013 with delta 0 on `grep -c 'unregister role=' ~/.task-force/radio/log`, sessions/ and log line count — this branch does not break the isolation invariant #205 added. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LzeuHXdqABg52t7ug7UDje
martin-conur
force-pushed
the
task/radio-troubleshooting-runbook
branch
from
September 14, 2026 22:23
cb29151 to
2c75ebb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a "When radio misbehaves" runbook to the radio docs — the consolidated
failure-path reference #191 asked for, documenting what actually shipped in
#187 / #188 / #189 / #190 plus the two tickets that landed after the issue was
filed (#197, #198).
Docs + tests only.
bin/radiois untouched (verified:git diff bin/radiois empty).The five symptoms
deliveredmeans a keystroke landed; thensessions/,radio orphans, the logradio checkis sitting unsubmitted in its prompt box"AUTO_SUBMIT=1, so it ended with LF not CR (#189)sessions/"unregistercounters, and check whether theradioonPATHis the post-#187 binaryradio unregisterand nothing happened"skippinglog lineBLOCKED_IDS=is named so anyone reading a session file knows what it isReading the log
~/.task-force/radio/logwas the single best diagnostic and entirely undocumented.The runbook gives the greps for delivery rate, wake failures bucketed by reason,
wipes vs. refusals, the #198 stdin shape, and the #188
tab_id_src=re-seed fields(
nonebeing the one value that leaves a role permanently unwakeable).Every grep was validated against a real 6,300-line production log, not written
from the source. Sample output that shaped the wording:
Two corrections to the spec
There is no dead-letter stage. The issue's item 4 asks where undelivered mail
goes "after radio re-seed loses TAB_ID and LOADOUT — unregister deletes the sidecars that exist to survive it #188's dead-letter stage" — radio re-seed loses TAB_ID and LOADOUT — unregister deletes the sidecars that exist to survive it #188 shipped the
.loadout/.agentsidecars, not a dead-letter queue, and
bin/radiohas no such concept. Therunbook documents the true behaviour (message written to the inbox before any
wake;
gcnever touchesinbox/and refuses to reclaim a mailbox holding unreadmail) and states plainly that no dead-letter queue exists — otherwise a reader
goes looking for a directory radio never creates. The substantive half of the
acceptance criterion, mail is never silently deleted, is documented and is
backed by
_inbox_emptyinbin/radio.kiro workers can't get auto-submit at all. Surfaced while writing the
AUTO_SUBMIT row: kiro's
task-workparses no--autoflag, soAUTO_MODEisnever set and
TASK_FORCE_AUTO_SUBMITis never injected for a kiro worker. Itswakes always end in LF and always need the keypress. Telling kiro users to
"relaunch with
task-work --auto" would send them after a flag that doesn'texist, so the kiro variant says so instead — and a test pins it.
Files (9 + tests)
README + all seven
*/steering/*-workflow.example.md+ this repo's dogfooded.claude/gh-workflow.md. The block is byte-identical across the four claudetemplates and byte-identical across the three kiro ones, differing only in the
AUTO_SUBMIT row and the closing delivery-guarantee paragraph. Loadout-neutral
throughout (#177).
Tests
tests/radio_runbook.bats— 12 tests. The load-bearing one asserts every logstring the runbook tells you to grep for is a literal substring of
bin/radio.That correspondence is invisible to every other test in the suite, so without it a
rename in
bin/radiowould quietly turn the runbook into a page of commands thatmatch nothing.
Mutation-checked — renaming
tab_id_src=→tab_source=inbin/radiofailstest 5 as intended:
Also replaces the
--owner ignoredsentinel inclaude_gh_task_init.bats/kiro_gh_task_init.bats. That assertion scans the whole rendered doc, so anordinary English word false-fails the moment the template prose uses it — which
this runbook does ("the same ids were ignored twice"). Now
sentinel-not-written,with a comment explaining why it must be non-prose. Test semantics unchanged.
Verification
./run_tests.sh— 1003 passing, 0 failingtools/check-drift.sh— 21 groups checked, greenshellcheck -x bin/radio— clean (unchanged)task-init claude-gh --workflow --forceandtask-init kiro-gh --workflow --forceinto fresh temp repos both render the new section
Upgrading
Re-run
task-init <loadout>to render the runbook into an existing repo's workflowdoc. An already-filled-in doc is never overwritten without
--force, so copy thesection by hand if yours is customised. No behaviour change.
Closes #191
🤖 Generated with Claude Code
https://claude.ai/code/session_01LzeuHXdqABg52t7ug7UDje