fix(bin): scope each task's temp root to its Firstmate home - #20
Merged
Merged
Conversation
fm-spawn put every task's temp root at /tmp/fm-<id>, a path not tied to the Firstmate home. Test spawns that were never torn down stranded it in /tmp, and two homes spawning one task id (a main home and a secondmate, or a test lab and the fleet) shared one root, so one home's teardown deleted the other's live root. The root now lives at state/<id>.tasktmp/ in the spawning home, next to the other per-task state directories, so equal ids in different homes never meet and a disposable home takes its roots with it. The pane's GOTMPDIR export is shell-quoted now that the path follows the home. A live task that recorded the legacy /tmp/fm-<id> keeps that root on relaunch, so its record stays valid and teardown removes the root it used. The live lab no longer removes /tmp/fm-<id> for its own ids. bin/fm-spawn.sh's header owns the path contract.
cloud-practitioner
force-pushed
the
fm/fm-spawn-tasktmp-home-scope
branch
from
October 1, 2026 02:17
d7ff932 to
21c7b56
Compare
This was referenced Oct 1, 2026
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.
Intent
Fix test hygiene found along the way: bin/fm-spawn.sh's per-task temp root /tmp/fm- is not scoped to the Firstmate home, so test spawns that are never torn down leak it into /tmp, and two homes could collide on the same task id. Scope each task's temp root to its Firstmate home.
What Changed
bin/fm-spawn.shnow creates each task's private 0700 temp root atstate/<id>.tasktmp/inside the spawning home instead of the shared/tmp/fm-<id>. It still records the path astasktmp=in the task meta and exportsGOTMPDIR(now shell-quoted) togotmp/under that root. When a task spawned before this change is relaunched, it keeps its recorded legacy/tmp/fm-<id>root, so teardown still removes the directory the task actually used. The path contract now lives in the script header.bin/fm-live-lab.shdownnow removes only the lab's home-hashed launch dirs; its task temp roots are deleted along with the lab root. Comments inbin/fm-teardown.shnow point to the spawn header. The home layout skill anddocs/configuration.mdlist the newstate/<id>.tasktmp/entry./tmp/fm-<id>by hand: fixtures and assertions use the home-scoped root. A new relaunch test checks that a legacy recorded root is kept and that a record with no root gets the home-scoped one.🤖 Generated with Claude Code
Risk Assessment
✅ Low: The change is well bounded. It moves the per-task temp root from /tmp/fm- to state/.tasktmp in the spawning home (realpath, created 0700, and the existing unsafe-root refusal still applies) and records it as tasktmp=. Teardown removes exactly the recorded root. Relaunch keeps a recorded legacy /tmp/fm- so no root is left behind. GOTMPDIR is now shell-quoted. Both fix rounds are verified: the legacy child removal is gone from forced teardown, and the relaunch fixtures record home-scoped roots, with /tmp/fm- left only in the rl91 legacy case, which registers it for cleanup.
Testing
I drove the real fm-spawn, fm-teardown, and fm-control relaunch in two throwaway fm-lab-* Herdr sessions. tmux is not installed on this host. The runs used marked disposable lab homes, a scratch git project, raw shell workers, and one real Claude worker. All five live scenarios passed: - home-scoped root creation and the pane GOTMPDIR export, including a home path with spaces - no collision between homes on the same id - teardown that only touches its own home - refusal of an unsafe pre-existing root - a legacy-root relaunch followed by its removal The sixth scenario, relaunching a record that has no tasktmp=, was only covered by the repo's fake-backend test suite, so it is marked untested. I also ran the three changed test files (fm-gotmp, fm-kimi-harness, and fm-control-relaunch), and all of them pass. Teardown removed both lab sessions, every scratch home, the treehouse pool entries my spawns created, and the leftover dirs from my relaunch-test run. The live default Herdr session was never touched. The change has no UI, so the evidence is CLI transcripts.Evidence: Live Herdr lab transcript: spawn, same-id collision, teardown, unsafe-root refusal
Source: Live Herdr lab transcript: spawn, same-id collision, teardown, unsafe-root refusal
Evidence: Driver script for the spawn/collision/teardown/refusal scenarios
Source: Driver script for the spawn/collision/teardown/refusal scenarios
Evidence: Live Herdr lab transcript: real Claude worker relaunched on a legacy /tmp/fm-<id> record
Source: Live Herdr lab transcript: real Claude worker relaunched on a legacy /tmp/fm-<id> record
Evidence: Driver script for the legacy-root relaunch scenario
Source: Driver script for the legacy-root relaunch scenario
Evidence: Key live output (same id in two homes)
Evidence: fm-kimi-harness test log
Source: fm-kimi-harness test log
Evidence: fm-gotmp test log
Source: fm-gotmp test log
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed ✅
tests/fm-control-relaunch.test.sh:1151- One test fixture still leaks a temp root into /tmp, which is the hygiene problem this change sets out to fix. The promoted-scout test (test_promoted_scout_relaunch_receives_the_current_delivery_contract, idrl-promoted-<mode>) writestasktmp=/tmp/fm-$idinto its meta and then runsfm-spawn --relaunch. Relaunch now deliberately keeps a recorded legacy root (bin/fm-spawn.sh:4389-4390), so each run creates/tmp/fm-rl-promoted-no-mistakes,/tmp/fm-rl-promoted-direct-PRand/tmp/fm-rl-promoted-local-only. Nothing removes them: the id is never added to TASK_TMPS and nothing tears the task down, so they build up in /tmp on every run. Two other fixtures have a related problem:add_ship_task(tests/fm-control-relaunch.test.sh:222/229) andadd_herdr_ship_task(tests/fm-control-relaunch.test.sh:2117/2130) also recordtasktmp=/tmp/fm-<fixed id>. They are cleaned at exit, but every relaunch test still uses a shared /tmp/fm-rlNN root that two concurrent runs of this suite on one host can share, and one run's cleanup can delete it under the other. That is the cross-home collision the intent describes. Fix: in all three fixtures, record the home-scoped root ($(cd "$home/state" && pwd -P)/$id.tasktmp) or leave tasktmp= out. Keep/tmp/fm-$idonly in the new legacy-root case (rl91), and register that path for cleanup there.🔧 Fix applied.
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
bin/fm-herdr-lab.sh provision fm-lab-tasktmp-*+ realbin/fm-spawn.sh <id> <proj> "sh -c 'echo GOTMPDIR=[$GOTMPDIR]; exec sleep 900'" --mode no-mistakes --yolo off --backend herdrin marked lab home A (driver: live-tasktmp-herdr-lab.sh)Same task id spawned in a second lab home whose path contains spaces; compared the recorded tasktmp=, stat of root/gotmp, and the GOTMPDIR export shown in each paneRealbin/fm-teardown.sh <id>in home A, then checked home B's root, then tore down BAdversarial: spawn with a symlink pre-planted at state/<id>.tasktmp, then with a pre-existing chmod 777 rootChecked that /tmp/fm-<id> was never created during spawn, collision, or teardownReal Claude (haiku) ship task in an isolated fm-lab-relaunch-* Herdr session; read GOTMPDIR from /proc/<pid>/environ; rewrote the record to tasktmp=/tmp/fm-<id>; ran realbin/fm-control.sh <id> relaunch --note ...; ranbin/fm-teardown.sh <id>(driver: live-legacy-relaunch-herdr-lab.sh)bash tests/fm-gotmp.test.shbash tests/fm-kimi-harness.test.sh(includes test_task_temp_root_is_scoped_to_the_spawning_home)bash tests/fm-control-relaunch.test.sh(includes test_relaunch_keeps_a_legacy_task_temp_root_and_scopes_a_missing_one)✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.