Skip to content

docs: 'When radio misbehaves' runbook — symptom→cause table, log diagnostics, post-#187/188/189/190 behavior - #204

Merged
martin-conur merged 2 commits into
mainfrom
task/radio-troubleshooting-runbook
Sep 14, 2026
Merged

martin-conur merged 2 commits into
mainfrom
task/radio-troubleshooting-runbook

Conversation

@martin-conur

Copy link
Copy Markdown
Owner

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/radio is untouched (verified: git diff bin/radio is empty).

The five symptoms

Symptom Runbook says
"I pinged the worker and nothing happened" Read the sender's outcome line first — only delivered means a keystroke landed; then sessions/, radio orphans, the log
"radio check is sitting unsubmitted in its prompt box" Wake delivered, not submitted — no AUTO_SUBMIT=1, so it ended with LF not CR (#189)
"A role keeps disappearing from sessions/" Session flapping; compare the three unregister counters, and check whether the radio on PATH is the post-#187 binary
"I ran radio unregister and nothing happened" Expected since #198 — it refused, with an stderr hint and a skipping log line
"The worker idles on a message in its own inbox" Fixed in #197; BLOCKED_IDS= is named so anyone reading a session file knows what it is

Reading the log

~/.task-force/radio/log was 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
(none being 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:

tab id unresolved    78     <- top queue reason
is busy              69
no session for       29

Two corrections to the spec

  1. 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/.agent
    sidecars, not a dead-letter queue, and bin/radio has no such concept. The
    runbook documents the true behaviour (message written to the inbox before any
    wake; gc never touches inbox/ and refuses to reclaim a mailbox holding unread
    mail) 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_empty in bin/radio.

  2. kiro workers can't get auto-submit at all. Surfaced while writing the
    AUTO_SUBMIT row: 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. Telling kiro users to
    "relaunch with task-work --auto" would send them after a flag that doesn't
    exist, 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 claude
templates
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 log
string 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/radio would quietly turn the runbook into a page of commands that
match nothing.

Mutation-checked — renaming tab_id_src=tab_source= in bin/radio fails
test 5 as intended:

not ok 5 every log string the runbook greps for exists in bin/radio

Also replaces the --owner ignored sentinel in claude_gh_task_init.bats /
kiro_gh_task_init.bats. That assertion scans the whole rendered doc, so an
ordinary 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.sh1003 passing, 0 failing
  • tools/check-drift.sh — 21 groups checked, green
  • shellcheck -x bin/radio — clean (unchanged)
  • task-init claude-gh --workflow --force and task-init kiro-gh --workflow --force
    into fresh temp repos both render the new section

Upgrading

Re-run task-init <loadout> to render the runbook into an existing repo's workflow
doc. An already-filled-in doc is never overwritten without --force, so copy the
section by hand if yours is customised. No behaviour change.

Closes #191

🤖 Generated with Claude Code

https://claude.ai/code/session_01LzeuHXdqABg52t7ug7UDje

martin-conur and others added 2 commits September 14, 2026 18:56
…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
martin-conur force-pushed the task/radio-troubleshooting-runbook branch from cb29151 to 2c75ebb Compare September 14, 2026 22:23
@martin-conur
martin-conur merged commit d79eb26 into main Sep 14, 2026
4 checks passed
@martin-conur
martin-conur deleted the task/radio-troubleshooting-runbook branch September 15, 2026 18:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: 'When radio misbehaves' runbook — symptom→cause table, log diagnostics, post-#187/188/189/190 behavior

1 participant