test suite wipes the developer's live radio session 57× per run: isolate $TASK_FORCE_HOME suite-wide (#203) - #205
Merged
Conversation
…ate $TASK_FORCE_HOME suite-wide task_done.bats and task_done_dispatcher.bats built setup() without any radio-home helper, so radio/task-done resolved their mailbox root to the real ~/.task-force. $TASK_FORCE_ROLE is inherited from the tab, so the `radio unregister --manual` those suites exercise — a wipe that is unconditional by design (#198) — landed on the runner's own session file: 51 + 6 = 57 wipes per ./run_tests.sh, matching the measured full-suite delta. Isolation is now the default rather than something each file must remember: - tests/setup_suite.bash runs once per bats invocation and exports a scratch $TASK_FORCE_HOME. bats resolves setup_suite.bash from the folder of the first test file, so a bare `bats tests/foo.bats` gets it too, not just ./run_tests.sh. - tests/helpers/radio_home.bash holds the guard; common.bash (loaded by every suite) aborts with an explanation when $TASK_FORCE_HOME is unset or points back at the real home. Skipped only on bats' pre-setup_suite gathering pass, where an unset value proves nothing. - Both suites get the per-test setup_task_force_home they were missing, and teardown_all now removes $TASK_FORCE_HOME only when the test created it, so it can't delete the run-scoped one. - tests/radio_home_isolation.bats drives real bats child-runs of both suites under a throwaway $HOME and asserts sessions/, mailbox/ and log stay absent (log pre-created, so the check is "nothing appended"), plus that a subverted $TASK_FORCE_HOME fails loudly. Verified delta 0 against `grep -c 'unregister role=' ~/.task-force/radio/log` for the full suite and for each suite individually. Tests only — no installed artifact changed, so no task-init re-run is needed. Closes #203 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DEuwfwn9bNWj4wc3WPDipT
martin-conur
added a commit
that referenced
this pull request
Sep 14, 2026
…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
added a commit
that referenced
this pull request
Sep 14, 2026
…nostics, post-#187/188/189/190 behavior (#204) * docs: 'When radio misbehaves' runbook — symptom→cause table, log diagnostics, 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 * radio runbook: name --manual as a cause of a role=/proceeding gap, not 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 --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Closes #203
Problem
./run_tests.shwiped the live radio session of whoever ran it, 57 times per run.tests/task_done.batsandtests/task_done_dispatcher.batsbuilt theirsetup()without any radio-home helper, so$TASK_FORCE_HOMEwas never overridden andradio/task-doneresolved their mailbox root to the developer's real~/.task-force.$TASK_FORCE_ROLEis inherited from the tab, so the destructive command those suites exercise —radio unregister --manual, a wipe that is unconditional by design (#198) — landed on the runner's own session file.Reproduced before touching anything, with
$HOMEredirected at a tempdir:51 (
task_done) + 6 (task_done_dispatcher) = the 57 the issue measured.Approach
The issue offered two options and called the second stronger — make isolation the default rather than something each file has to remember. This does that, with the loud guard as a second layer.
One deviation from the spec text: the helper is named
setup_task_force_home(tests/helpers/common.bash:133), notsetup_radio_home.tests/setup_suite.bash(new) rather thanrun_tests.sh. bats resolvessetup_suite.bashfrom the folder of the first test file and runs it once per invocation, so a barebats tests/foo.batsgets the isolation too — not just./run_tests.sh. It honours a caller-supplied$TASK_FORCE_HOMEbut only after proving it isolated.tests/helpers/radio_home.bash(new) holdstask_force_home_is_isolated/require_isolated_task_force_home, shared bysetup_suite.bashandcommon.bashrather than copied.tests/helpers/common.bash— every suite loads it, so it's the chokepoint: the run aborts with an explanation if$TASK_FORCE_HOMEis unset or aimed at the real home. Enforcement is skipped on bats' test-gathering pass (BATS_TEST_NAME=source), which runs beforesetup_suiteand where an unset value proves nothing; a value that is set and wrong is rejected on every pass.Both suites get the per-test
setup_task_force_homethey were missing, andteardown_allnow removes$TASK_FORCE_HOMEonly when the test created it (newTASK_FORCE_HOME_OWNEDflag) — otherwise a per-test teardown would delete the run-scoped home out from under the rest of the run. The five now-redundant in-test calls intask_done.batsare dropped.Acceptance
./run_tests.sh→ delta 0 ongrep -c 'unregister role='./run_tests.sh task_done→ delta 0./run_tests.sh task_done_dispatcher→ delta 0$HOME/.task-force/—sessions/,mailbox/,logtogether$HOMEtests/radio_home_isolation.bats(9 tests) is the standing guard. It drives real bats child-runs of both suites under a throwaway$HOMEand asserts all three paths stay absent, with the log pre-created so the check is "nothing was appended", not merely "nothing was created". It also stands up a probe suite carrying onlysetup_suite.bash+ the helper to prove a file that never callssetup_task_force_homestill comes out isolated, and asserts that pointing$TASK_FORCE_HOMEat the real home aborts loudly.Confirmed as real regression tests: with both layers reverted, 7 of the 9 fail; with the fix in place, 9/9 pass.
Green
./run_tests.sh— 1000/1000, 0 failures (991 before + 9 new)tools/check-drift.sh— 21 groups checked, cleanshellcheck -x tests/helpers/*.bash tests/setup_suite.bash run_tests.sh— clean.github/workflows/ci.ymllint list extended to covertests/setup_suite.bash(it sits outside the existingtests/helpers/*.bashglob)Checklist
## [Unreleased] / Fixed. Tests + README + CI only, notask-initre-run needed; no installer-written artifact changed.radio_home_isolation.batsrow in the suite table, the two new infrastructure files, and a pointer in the Contributing test pattern. The foursteering/*.example.mdtemplates and the.claude/*-workflow.mddocs are untouched: nothing model-facing or user-visible in the loadouts changed.🤖 Generated with Claude Code
https://claude.ai/code/session_01DEuwfwn9bNWj4wc3WPDipT