diff --git a/bin/fm-brief.sh b/bin/fm-brief.sh index e0f2ec78..bd88c043 100755 --- a/bin/fm-brief.sh +++ b/bin/fm-brief.sh @@ -154,6 +154,9 @@ The report is the only thing that survives, so anything worth keeping must be in 5. If you hit the same obstacle twice, append \`blocked: {why}\` and stop; firstmate will help. 6. If a decision belongs to a human (product choices, destructive actions), append \`needs-decision: {summary of options}\` and stop. Firstmate will reply with the decision. +7. Never stop, restart, or update the shared \`no-mistakes\` daemon - it is one instance serving + every lane/home, so restarting it kills other lanes' in-flight pipeline runs. On ANY no-mistakes + daemon error, append \`blocked: {the daemon error}\` and stop; only firstmate manages the daemon. # Definition of done Write your findings to \`$DATA/$ID/report.md\`. @@ -251,6 +254,9 @@ $RULE1 5. If you hit the same obstacle twice, append \`blocked: {why}\` and stop; firstmate will help. 6. If a decision belongs to a human (product choices, destructive actions, ask-user findings), append \`needs-decision: {summary of options}\` and stop. Firstmate will reply with the decision. +7. Never stop, restart, or update the shared \`no-mistakes\` daemon - it is one instance serving + every lane/home, so restarting it kills other lanes' in-flight pipeline runs. On ANY no-mistakes + daemon error, append \`blocked: {the daemon error}\` and stop; only firstmate manages the daemon. # Project memory If \`AGENTS.md\` or \`CLAUDE.md\` already exists, or if this task produced durable project-intrinsic knowledge, run \`$FM_ROOT/bin/fm-ensure-agents-md.sh .\` in the worktree. diff --git a/tests/fm-gotmp.test.sh b/tests/fm-gotmp.test.sh index 8ea2a70c..3ebf0b7d 100755 --- a/tests/fm-gotmp.test.sh +++ b/tests/fm-gotmp.test.sh @@ -128,8 +128,10 @@ test_teardown_removes_tasktmp_dir() { fake=$(make_fake_root "$id" "$task_tmp") # Sanity: dir + contents exist before teardown. [ -d "$task_tmp/gotmp" ] || fail "precondition: gotmp missing before teardown" - # Run the REAL teardown against the fake root. - bash "$fake/bin/fm-teardown.sh" "$id" >/dev/null 2>&1 \ + # Run the REAL teardown against the fake root. FM_HOME is pinned to the fake + # root so an ambient FM_HOME in the caller's environment cannot redirect + # teardown at a real home instead of this fixture. + FM_HOME="$fake" bash "$fake/bin/fm-teardown.sh" "$id" >/dev/null 2>&1 \ || fail "teardown exited non-zero with a valid tasktmp" [ ! -e "$task_tmp" ] \ || fail "teardown did not remove the tasktmp dir ($task_tmp still exists)" @@ -169,7 +171,7 @@ kind=ship mode=no-mistakes yolo=off META - bash "$fake/bin/fm-teardown.sh" "$id" >/dev/null 2>&1 \ + FM_HOME="$fake" bash "$fake/bin/fm-teardown.sh" "$id" >/dev/null 2>&1 \ || fail "teardown exited non-zero when tasktmp= was absent" pass "fm-teardown skips gracefully when tasktmp= is absent (backward compat)" } @@ -182,7 +184,7 @@ test_teardown_skips_gracefully_when_dir_missing() { [ ! -e "$task_tmp" ] || fail "precondition: task_tmp should not exist yet" local fake fake=$(make_fake_root "$id" "$task_tmp") - bash "$fake/bin/fm-teardown.sh" "$id" >/dev/null 2>&1 \ + FM_HOME="$fake" bash "$fake/bin/fm-teardown.sh" "$id" >/dev/null 2>&1 \ || fail "teardown exited non-zero when tasktmp dir was missing" [ ! -e "$task_tmp" ] || fail "teardown created/left the tasktmp dir unexpectedly" pass "fm-teardown skips gracefully when tasktmp= points to a nonexistent dir" diff --git a/tests/fm-turnend-guard.test.sh b/tests/fm-turnend-guard.test.sh index 5edbc3ac..7161be48 100755 --- a/tests/fm-turnend-guard.test.sh +++ b/tests/fm-turnend-guard.test.sh @@ -94,6 +94,11 @@ install_guard_scripts() { # A primary-shaped checkout: plain (non-worktree) git repo, AGENTS.md, bin/, # state/ - everything the hook's scoping check requires to treat it as primary. +# Returns the CANONICALIZED path (cd && pwd): TMPDIR on macOS ends in a trailing +# slash, so a path built by string concatenation off it carries a double slash +# that string-compares unequal to the same path normalized elsewhere (e.g. +# record_watcher_lock's `cd "$dir" && pwd`), which would falsely fail the +# fm-home identity match the watcher-lock tests depend on. make_primary_dir() { local dir=$1 mkdir -p "$dir/state" @@ -101,14 +106,14 @@ make_primary_dir() { git -C "$dir" commit -q --allow-empty -m init : > "$dir/AGENTS.md" install_guard_scripts "$dir" - printf '%s\n' "$dir" + (cd "$dir" && pwd) } # Same shape as primary, plus the .fm-secondmate-home marker bin/fm-home-seed.sh # writes at seed time (regardless of treehouse-lease or git-clone acquisition). make_secondmate_dir() { local dir=$1 - make_primary_dir "$dir" >/dev/null + dir=$(make_primary_dir "$dir") printf 'sm-test-1\n' > "$dir/.fm-secondmate-home" printf '%s\n' "$dir" } @@ -127,7 +132,10 @@ make_crewmate_worktree_dir() { run_hook() { local dir=$1 stop_active=$2 - printf '{"stop_hook_active":%s}' "$stop_active" | bash "$dir/bin/fm-turnend-guard.sh" 2>&1 + # Pin FM_HOME to the scenario dir so an ambient FM_HOME in the caller's + # environment cannot redirect the hook at a real home's state instead of + # this fixture. + printf '{"stop_hook_active":%s}' "$stop_active" | FM_HOME="$dir" bash "$dir/bin/fm-turnend-guard.sh" 2>&1 } nonexistent_pid() {