From 7b05a33d302ea4947f5f9b82c734704fe6030851 Mon Sep 17 00:00:00 2001 From: Dongkeun Lee Date: Mon, 27 Jul 2026 11:04:30 -0400 Subject: [PATCH 01/14] fix: close inherited fds in bounded commands and handle already-returned worktrees Defect 1: fm_run_bounded's command child inherits all fds from the parent process. The boundary walker in treehouse return opens one fd per worktree directory; on large trees the inherited fds consume headroom and cause EMFILE. Close every fd above stderr in the command child before exec so the bounded command starts with a clean table. Defect 2: after a manual treehouse return (workaround for defect 1), teardown refuses because the lease is cleared. Recognize an already-returned worktree as a terminal state when all other identity checks pass and work is provably landed, skipping only the treehouse return step. Refuse when work is NOT landed (fail-closed). Add regression tests for both defects plus an already-returned-but- unlanded refusal test. --- bin/fm-process-tree-lib.sh | 11 ++++ bin/fm-teardown.sh | 97 ++++++++++++++++++++++++++------ tests/fm-teardown.test.sh | 111 +++++++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 16 deletions(-) diff --git a/bin/fm-process-tree-lib.sh b/bin/fm-process-tree-lib.sh index 16e3ded145..795c25bf34 100644 --- a/bin/fm-process-tree-lib.sh +++ b/bin/fm-process-tree-lib.sh @@ -184,6 +184,17 @@ fm_run_bounded() { if (!$command) { close $status_write; close $finish_read; + # Close inherited fds above stderr so the bounded command starts + # with a clean fd table. The boundary walker in + # fm_checkout_treehouse_return_locked opens one fd per directory + # in the worktree; inherited fds from the parent bash process + # consume headroom and cause EMFILE on large trees. + if (opendir my $devfd, "/dev/fd") { + my @inherited = grep { $_ > 2 } + map { /^(\d+)$/ ? $1 : () } readdir $devfd; + closedir $devfd; + POSIX::close($_) for @inherited; + } $SIG{HUP} = "DEFAULT"; $SIG{INT} = "DEFAULT"; $SIG{QUIT} = "DEFAULT"; diff --git a/bin/fm-teardown.sh b/bin/fm-teardown.sh index ea03eda10b..7d90374edc 100755 --- a/bin/fm-teardown.sh +++ b/bin/fm-teardown.sh @@ -1034,6 +1034,37 @@ except (OSError, ValueError, TypeError, KeyError, json.JSONDecodeError) as error PY } +treehouse_lease_is_cleared() { + local worktree=$1 state + state=$(treehouse_state_for_worktree "$worktree") || return 1 + python3 - "$state" "$(cd "$worktree" 2>/dev/null && pwd -P || printf '%s' "$worktree")" <<'PY' +import json +import os +import sys + +state_path, expected_path = sys.argv[1:] +try: + with open(state_path, encoding="utf-8") as stream: + state = json.load(stream) + worktrees = state.get("worktrees", []) + if not isinstance(worktrees, list): + raise SystemExit(1) + matches = [ + e for e in worktrees + if isinstance(e, dict) and isinstance(e.get("path"), str) + and os.path.realpath(e["path"]) == expected_path + ] + if not matches: + raise SystemExit(0) + entry = matches[0] + if entry.get("leased") is True: + raise SystemExit(1) + raise SystemExit(0) +except (OSError, ValueError, json.JSONDecodeError): + raise SystemExit(1) +PY +} + require_treehouse_return_authority() { local worktree=$1 project=$2 worktree_root project_root worktree_common project_common worktree_root=$(exact_git_worktree_root "$worktree") || return 1 @@ -1083,7 +1114,14 @@ validate_teardown_target_identity() { echo "error: teardown worktree is not registered to the recorded project: $worktree_root" >&2 return 1 } - require_treehouse_task_lease "$worktree_root" "firstmate-$ID" + if require_treehouse_task_lease "$worktree_root" "firstmate-$ID"; then + return 0 + fi + if treehouse_lease_is_cleared "$worktree_root"; then + echo "teardown: worktree lease already cleared (worktree may have been returned externally): $worktree_root" >&2 + return "$TEARDOWN_WORKTREE_ALREADY_RETURNED" + fi + return 1 } retry_wait_secs_is_valid() { @@ -1104,6 +1142,7 @@ fi STALE_WORKTREE_LOCK_RETRY_WAIT_SECS=$TREEHOUSE_RETURN_LOCK_RETRY_WAIT_SECS TEARDOWN_TREEHOUSE_LOCK_REFUSED=2 TEARDOWN_WORKTREE_SAFETY_LOCK_BLOCKED=3 +TEARDOWN_WORKTREE_ALREADY_RETURNED=4 # True when treehouse/git stderr shows the transient index.lock "File exists" race. # Other return failures must not enter the retry path. @@ -3929,7 +3968,19 @@ if [ "$KIND" = scout ]; then fi fi -[ "$KIND" = secondmate ] || validate_teardown_target_identity || exit 1 +WORKTREE_ALREADY_RETURNED=0 +if [ "$KIND" != secondmate ]; then + if validate_teardown_target_identity; then + : + else + _vtid_rc=$? + if [ "$_vtid_rc" -eq "$TEARDOWN_WORKTREE_ALREADY_RETURNED" ]; then + WORKTREE_ALREADY_RETURNED=1 + else + exit 1 + fi + fi +fi PROBE_HOME= ENDPOINT_HOME=$(fm_backend_endpoint_home "$BACKEND" "$KIND" "$FM_HOME" "$HOME_PATH") @@ -4014,14 +4065,26 @@ post_quiescence_safety_refusal() { echo "The task endpoint has already been shut down; the worktree and task metadata are preserved for a safe retry." >&2 } +validate_teardown_target_identity_or_returned() { + if validate_teardown_target_identity; then + return 0 + fi + _vtid_rc=$? + if [ "$_vtid_rc" -eq "$TEARDOWN_WORKTREE_ALREADY_RETURNED" ]; then + WORKTREE_ALREADY_RETURNED=1 + return 0 + fi + return "$_vtid_rc" +} + if [ "$DIRECT_SPAWN_CLEANUP" = pending ]; then if [ "$DIRECT_SPAWN_ENDPOINT" != not-created ]; then quiesce_retained_direct_spawn_endpoint || exit 1 fi - validate_teardown_target_identity || { post_quiescence_safety_refusal; exit 1; } + validate_teardown_target_identity_or_returned || { post_quiescence_safety_refusal; exit 1; } elif [ "$KIND" != secondmate ]; then quiesce_task_endpoint || exit 1 - validate_teardown_target_identity || { post_quiescence_safety_refusal; exit 1; } + validate_teardown_target_identity_or_returned || { post_quiescence_safety_refusal; exit 1; } fi if [ "$BACKEND" = orca ] && [ "$KIND" != secondmate ]; then @@ -4068,7 +4131,7 @@ if [ "$KIND" = secondmate ] && [ "$FORCE" = "--force" ]; then cleanup_firstmate_home_children "$HOME_PATH" || exit 1 fi -[ "$KIND" = secondmate ] || validate_teardown_target_identity || exit 1 +[ "$KIND" = secondmate ] || validate_teardown_target_identity_or_returned || exit 1 remove_orca_worktree_locked() { local branch=HEAD boundary_token @@ -4097,18 +4160,20 @@ if [ "$BACKEND" = orca ] && [ "$KIND" != secondmate ]; then fi fm_checkout_lock_run "$WT" "$CHECKOUT_LOCK_ROOT" remove_orca_worktree_locked || exit 1 elif [ -d "$WT" ] && [ "$KIND" != secondmate ]; then - # Kills remaining processes in the worktree (including the agent), resets, returns - # to pool. treehouse resolves the pool from the working directory, so run it from - # the project. teardown_treehouse_return tolerates transient and stale git locks - # left by a killed crewmate process; see the script header for retry and stale-lock proof. - post_lock_cleanup_check= - if [ "$KIND" != secondmate ]; then - post_lock_cleanup_check=validate_worktree_teardown_safety + if [ "$WORKTREE_ALREADY_RETURNED" = 1 ]; then + cleanup_returned_worktree \ + "$(git -C "$WT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)" \ + "$WT" "$PROJ" + else + post_lock_cleanup_check= + if [ "$KIND" != secondmate ]; then + post_lock_cleanup_check=validate_worktree_teardown_safety + fi + teardown_treehouse_return "$WT" "$PROJ" "worktree" "firstmate-$ID" "$post_lock_cleanup_check" cleanup_returned_worktree || { + echo "error: treehouse return failed for worktree $WT; teardown aborted" >&2 + exit 1 + } fi - teardown_treehouse_return "$WT" "$PROJ" "worktree" "firstmate-$ID" "$post_lock_cleanup_check" cleanup_returned_worktree || { - echo "error: treehouse return failed for worktree $WT; teardown aborted" >&2 - exit 1 - } fi if [ "$DIRECT_SPAWN_CLEANUP" = pending ] && [ -n "$DIRECT_SPAWN_BACKUP" ]; then diff --git a/tests/fm-teardown.test.sh b/tests/fm-teardown.test.sh index ea5751c55d..12c319aa5c 100755 --- a/tests/fm-teardown.test.sh +++ b/tests/fm-teardown.test.sh @@ -49,6 +49,11 @@ # (w) index.lock mtime read failure -> lock kept, REFUSE # (x) transient lock cleared after first failed return -> retry ALLOW # (y) persistent lock (never clears, not provably stale) -> REFUSE loudly +# +# Also covers already-returned worktree (external treehouse return) and fd leak: +# (z1) already-returned worktree + landed work -> ALLOW (cleanup-only) +# (z2) already-returned worktree + unlanded work -> REFUSE (safety) +# (z3) low ulimit -n + many directories in worktree -> ALLOW (fd leak fix) set -u # shellcheck source=tests/lib.sh disable=SC1091 @@ -90,6 +95,33 @@ with open(state, "w", encoding="utf-8") as stream: PY } +write_treehouse_unleased() { + local worktree=$1 slot pool state + slot=$(cd "$(dirname "$worktree")" && pwd -P) + pool=$(cd "$(dirname "$slot")" && pwd -P) + state="$pool/treehouse-state.json" + python3 - "$state" "$(cd "$worktree" && pwd -P)" <<'PY' +import json +import sys + +state, path = sys.argv[1:] +with open(state, "w", encoding="utf-8") as stream: + json.dump( + { + "worktrees": [ + { + "name": "1", + "path": path, + "leased": False, + "lease_holder": "", + } + ] + }, + stream, + ) +PY +} + prepare_secondmate_home_fixture() { local case_dir=$1 id=${2:-task-x1} root_default default root_tip exclude home_abs mkdir -p "$case_dir/data" "$case_dir/wt/data" "$case_dir/wt/state" "$case_dir/wt/config" \ @@ -4320,6 +4352,82 @@ test_secondmate_registry_updates_are_locked_and_literal() { pass "secondmate registry updates are serialized and compare ids literally" } +test_already_returned_worktree_with_landed_work_allows() { + local case_dir rc + case_dir=$(make_case already-returned-landed) + write_meta "$case_dir" no-mistakes ship + wt_commit "$case_dir" "fix the thing" + git -C "$case_dir/wt" push -q origin fm/task-x1 + + write_treehouse_unleased "$case_dir/wt" + + set +e + run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" + rc=$? + set -e + + [ "$rc" -eq 0 ] || fail "already-returned-landed: teardown should succeed when work is on a remote: $(cat "$case_dir/stderr")" + assert_grep 'lease already cleared' "$case_dir/stderr" \ + "already-returned-landed: teardown did not log the cleared-lease detection" + pass "already-returned worktree with landed work tears down cleanly" +} + +test_already_returned_worktree_with_unlanded_work_refuses() { + local case_dir rc + case_dir=$(make_case already-returned-unlanded) + write_meta "$case_dir" no-mistakes ship + wt_commit_file "$case_dir" unlanded.txt "content not in main" "add unlanded work" + + write_treehouse_unleased "$case_dir/wt" + + set +e + run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" + rc=$? + set -e + + [ "$rc" -ne 0 ] || fail "already-returned-unlanded: teardown should refuse when work is not landed" + assert_grep 'REFUSED' "$case_dir/stderr" \ + "already-returned-unlanded: teardown did not print REFUSED" + pass "already-returned worktree with unlanded work is refused" +} + +test_fd_leak_under_low_ulimit() { + local case_dir rc dir_count i + case_dir=$(make_case fd-leak-ulimit) + write_meta "$case_dir" no-mistakes ship + wt_commit "$case_dir" "fix the thing" + git -C "$case_dir/wt" push -q origin fm/task-x1 + + dir_count=200 + i=0 + while [ "$i" -lt "$dir_count" ]; do + mkdir -p "$case_dir/wt/deep/dir_$i" + : > "$case_dir/wt/deep/dir_$i/.keep" + i=$((i + 1)) + done + git -C "$case_dir/wt" add -A + git -C "$case_dir/wt" -c user.email=t@t -c user.name=t commit -q -m "add dirs" + git -C "$case_dir/wt" push -q origin fm/task-x1 + + set +e + ( + ulimit -n 256 2>/dev/null || exit 99 + run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" + ) + rc=$? + set -e + + [ "$rc" -eq 0 ] || fail "fd-leak-ulimit: teardown failed under low ulimit (rc=$rc): $(cat "$case_dir/stderr")" + pass "teardown succeeds under low ulimit -n with many directories (fd leak fix)" +} + +if [ "${FM_TEST_FOCUSED:-}" = already-returned-and-fd-leak ]; then + test_already_returned_worktree_with_landed_work_allows + test_already_returned_worktree_with_unlanded_work_refuses + test_fd_leak_under_low_ulimit + exit 0 +fi + if [ "${FM_TEST_FOCUSED:-}" = tasktmp-safety ]; then test_teardown_refuses_unsafe_tasktmp_metadata exit 0 @@ -4574,3 +4682,6 @@ test_transient_index_lock_clears_after_first_attempt_and_retry_succeeds test_persistent_index_lock_exhausts_retries_and_refuses_loudly test_empty_retry_wait_uses_default_without_aborting test_fractional_legacy_retry_wait_refuses_without_arithmetic_error +test_already_returned_worktree_with_landed_work_allows +test_already_returned_worktree_with_unlanded_work_refuses +test_fd_leak_under_low_ulimit From 551d9935c2d8b951ccbb333836b5372ce7a12bfd Mon Sep 17 00:00:00 2001 From: Dongkeun Lee Date: Mon, 27 Jul 2026 11:14:16 -0400 Subject: [PATCH 02/14] no-mistakes(review): Serialize returned-worktree cleanup and reject ambiguous leases --- bin/fm-teardown.sh | 24 ++++++++-- tests/fm-teardown.test.sh | 92 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 106 insertions(+), 10 deletions(-) diff --git a/bin/fm-teardown.sh b/bin/fm-teardown.sh index 7d90374edc..efba160bb7 100755 --- a/bin/fm-teardown.sh +++ b/bin/fm-teardown.sh @@ -1056,8 +1056,10 @@ try: ] if not matches: raise SystemExit(0) + if len(matches) != 1: + raise SystemExit(1) entry = matches[0] - if entry.get("leased") is True: + if entry.get("leased") is not False: raise SystemExit(1) raise SystemExit(0) except (OSError, ValueError, json.JSONDecodeError): @@ -1357,6 +1359,21 @@ cleanup_returned_worktree() { remove_worktree_compatibility_artifacts "$worktree" "returned worktree" } +cleanup_already_returned_worktree_locked() { + local branch + treehouse_lease_is_cleared "$WT" || { + echo "error: worktree lease is no longer provably cleared; retaining $WT" >&2 + return 1 + } + validate_worktree_teardown_safety || return 1 + treehouse_lease_is_cleared "$WT" || { + echo "error: worktree lease changed during final safety checks; retaining $WT" >&2 + return 1 + } + branch=$(git -C "$WT" rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1 + cleanup_returned_worktree "$branch" "$WT" "$PROJ" +} + validate_worktree_teardown_safety() { local dirty_raw dirty unpushed_raw unpushed DEFAULT unmerged_raw unmerged branch stash_list [ -d "$WT" ] || return 0 @@ -4161,9 +4178,8 @@ if [ "$BACKEND" = orca ] && [ "$KIND" != secondmate ]; then fm_checkout_lock_run "$WT" "$CHECKOUT_LOCK_ROOT" remove_orca_worktree_locked || exit 1 elif [ -d "$WT" ] && [ "$KIND" != secondmate ]; then if [ "$WORKTREE_ALREADY_RETURNED" = 1 ]; then - cleanup_returned_worktree \ - "$(git -C "$WT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)" \ - "$WT" "$PROJ" + fm_checkout_lock_run "$WT" "$CHECKOUT_LOCK_ROOT" \ + cleanup_already_returned_worktree_locked || exit 1 else post_lock_cleanup_check= if [ "$KIND" != secondmate ]; then diff --git a/tests/fm-teardown.test.sh b/tests/fm-teardown.test.sh index 12c319aa5c..13f7d48fa5 100755 --- a/tests/fm-teardown.test.sh +++ b/tests/fm-teardown.test.sh @@ -122,6 +122,39 @@ with open(state, "w", encoding="utf-8") as stream: PY } +write_treehouse_duplicate_mixed_lease() { + local worktree=$1 slot pool state + slot=$(cd "$(dirname "$worktree")" && pwd -P) + pool=$(cd "$(dirname "$slot")" && pwd -P) + state="$pool/treehouse-state.json" + python3 - "$state" "$(cd "$worktree" && pwd -P)" <<'PY' +import json +import sys + +state, path = sys.argv[1:] +with open(state, "w", encoding="utf-8") as stream: + json.dump( + { + "worktrees": [ + { + "name": "1", + "path": path, + "leased": False, + "lease_holder": "", + }, + { + "name": "duplicate", + "path": path, + "leased": True, + "lease_holder": "firstmate-other-task", + }, + ] + }, + stream, + ) +PY +} + prepare_secondmate_home_fixture() { local case_dir=$1 id=${2:-task-x1} root_default default root_tip exclude home_abs mkdir -p "$case_dir/data" "$case_dir/wt/data" "$case_dir/wt/state" "$case_dir/wt/config" \ @@ -607,12 +640,11 @@ git_index_lock_path() { } checkout_lock_path() { - local dir=$1 lock_root=$2 common key - common=$(git -C "$dir" rev-parse --git-common-dir) - case "$common" in /*) ;; *) common="$dir/$common" ;; esac - common=$(cd "$common" && pwd -P) - key=$(printf '%s' "$common" | shasum -a 256 | awk '{print substr($1,1,24)}') - printf '%s/%s.lock\n' "$lock_root" "$key" + local dir=$1 lock_root=$2 + bash -c ' + . "$1/bin/fm-checkout-lock-lib.sh" + fm_checkout_lock_path "$2" "$3" + ' _ "$ROOT" "$dir" "$lock_root" } # fakebin/lsof stub: no process ever holds anything open (lsof's not-found exit @@ -4391,6 +4423,50 @@ test_already_returned_worktree_with_unlanded_work_refuses() { pass "already-returned worktree with unlanded work is refused" } +test_already_returned_worktree_cleanup_honors_checkout_lock() { + local case_dir rc lock + case_dir=$(make_case already-returned-lock) + write_meta "$case_dir" no-mistakes ship + wt_commit "$case_dir" "fix the thing" + git -C "$case_dir/wt" push -q origin fm/task-x1 + write_treehouse_unleased "$case_dir/wt" + lock=$(checkout_lock_path "$case_dir/wt" "$case_dir/checkout-locks") + mkdir -p "$lock" + printf '%s\n' "$$" > "$lock/pid" + + set +e + run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" + rc=$? + set -e + + [ "$rc" -ne 0 ] || fail "already-returned-lock: teardown bypassed the checkout lock" + assert_present "$case_dir/wt" "already-returned-lock: teardown removed the contended worktree" + assert_present "$case_dir/state/task-x1.meta" "already-returned-lock: teardown removed task metadata" + assert_grep "checkout mutation already running for $case_dir/wt (pid $$)" \ + "$case_dir/stderr" "already-returned-lock: teardown did not surface checkout lock contention" + rm -rf "$lock" + pass "already-returned cleanup holds the shared checkout lock" +} + +test_duplicate_treehouse_entries_refuse_cleared_lease() { + local case_dir rc + case_dir=$(make_case already-returned-duplicate) + write_meta "$case_dir" no-mistakes ship + wt_commit "$case_dir" "fix the thing" + git -C "$case_dir/wt" push -q origin fm/task-x1 + write_treehouse_duplicate_mixed_lease "$case_dir/wt" + + set +e + run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" + rc=$? + set -e + + [ "$rc" -ne 0 ] || fail "already-returned-duplicate: duplicate lease entries were accepted" + assert_present "$case_dir/wt" "already-returned-duplicate: teardown removed the ambiguous worktree" + assert_present "$case_dir/state/task-x1.meta" "already-returned-duplicate: teardown removed task metadata" + pass "duplicate Treehouse entries fail closed" +} + test_fd_leak_under_low_ulimit() { local case_dir rc dir_count i case_dir=$(make_case fd-leak-ulimit) @@ -4424,6 +4500,8 @@ test_fd_leak_under_low_ulimit() { if [ "${FM_TEST_FOCUSED:-}" = already-returned-and-fd-leak ]; then test_already_returned_worktree_with_landed_work_allows test_already_returned_worktree_with_unlanded_work_refuses + test_already_returned_worktree_cleanup_honors_checkout_lock + test_duplicate_treehouse_entries_refuse_cleared_lease test_fd_leak_under_low_ulimit exit 0 fi @@ -4684,4 +4762,6 @@ test_empty_retry_wait_uses_default_without_aborting test_fractional_legacy_retry_wait_refuses_without_arithmetic_error test_already_returned_worktree_with_landed_work_allows test_already_returned_worktree_with_unlanded_work_refuses +test_already_returned_worktree_cleanup_honors_checkout_lock +test_duplicate_treehouse_entries_refuse_cleared_lease test_fd_leak_under_low_ulimit From 0e8d4ff9ab697f753a9a8e4e20436878b8ce0a39 Mon Sep 17 00:00:00 2001 From: Dongkeun Lee Date: Mon, 27 Jul 2026 13:40:42 -0400 Subject: [PATCH 03/14] chore: trigger pipeline rerun (test step agent allocation failure) From 4c7917e9a3ca04c15dd2be3ba42c12f97d681304 Mon Sep 17 00:00:00 2001 From: Dongkeun Lee Date: Mon, 27 Jul 2026 13:45:59 -0400 Subject: [PATCH 04/14] no-mistakes(review): Harden externally returned Treehouse cleanup --- bin/fm-teardown.sh | 34 ++++++++++++++++++++++++++++++---- tests/fm-teardown.test.sh | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/bin/fm-teardown.sh b/bin/fm-teardown.sh index efba160bb7..a8275e7442 100755 --- a/bin/fm-teardown.sh +++ b/bin/fm-teardown.sh @@ -1054,13 +1054,15 @@ try: if isinstance(e, dict) and isinstance(e.get("path"), str) and os.path.realpath(e["path"]) == expected_path ] - if not matches: - raise SystemExit(0) if len(matches) != 1: raise SystemExit(1) entry = matches[0] if entry.get("leased") is not False: raise SystemExit(1) + if entry.get("lease_holder") not in ("", None): + raise SystemExit(1) + if entry.get("destroying") is True: + raise SystemExit(1) raise SystemExit(0) except (OSError, ValueError, json.JSONDecodeError): raise SystemExit(1) @@ -1359,8 +1361,19 @@ cleanup_returned_worktree() { remove_worktree_compatibility_artifacts "$worktree" "returned worktree" } +cleanup_recovered_worktree() { + local branch=$1 worktree=$2 project=$3 + if [ "$branch" != "HEAD" ]; then + git -C "$project" branch -D "$branch" >/dev/null 2>&1 || { + echo "error: recovered worktree task branch could not be deleted: $branch" >&2 + return 1 + } + fi + remove_worktree_compatibility_artifacts "$worktree" "returned worktree" +} + cleanup_already_returned_worktree_locked() { - local branch + local branch out return_status treehouse_lease_is_cleared "$WT" || { echo "error: worktree lease is no longer provably cleared; retaining $WT" >&2 return 1 @@ -1371,7 +1384,20 @@ cleanup_already_returned_worktree_locked() { return 1 } branch=$(git -C "$WT" rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1 - cleanup_returned_worktree "$branch" "$WT" "$PROJ" + validate_removal_tree_boundaries "$WT" "worktree" || return 1 + if out=$(fm_checkout_treehouse_return_locked "$WT" "$CHECKOUT_LOCK_ROOT" "$PROJ" 2>&1); then + [ -n "$out" ] && printf '%s\n' "$out" + else + return_status=$? + [ -n "$out" ] && printf '%s\n' "$out" >&2 + echo "error: failed to complete the externally-started Treehouse return for $WT" >&2 + return "$return_status" + fi + treehouse_lease_is_cleared "$WT" || { + echo "error: Treehouse return did not leave a provably returned pool slot; retaining task metadata" >&2 + return 1 + } + cleanup_recovered_worktree "$branch" "$WT" "$PROJ" } validate_worktree_teardown_safety() { diff --git a/tests/fm-teardown.test.sh b/tests/fm-teardown.test.sh index 13f7d48fa5..e0146cc38c 100755 --- a/tests/fm-teardown.test.sh +++ b/tests/fm-teardown.test.sh @@ -122,6 +122,14 @@ with open(state, "w", encoding="utf-8") as stream: PY } +write_treehouse_without_worktree() { + local worktree=$1 slot pool state + slot=$(cd "$(dirname "$worktree")" && pwd -P) + pool=$(cd "$(dirname "$slot")" && pwd -P) + state="$pool/treehouse-state.json" + printf '%s\n' '{"worktrees":[]}' > "$state" +} + write_treehouse_duplicate_mixed_lease() { local worktree=$1 slot pool state slot=$(cd "$(dirname "$worktree")" && pwd -P) @@ -239,6 +247,7 @@ make_case() { echo "secondmate parent was not quiesced before child cleanup: $FM_EXPECT_PARENT_QUIESCED" >&2 exit 98 } +[ -z "${FM_FAKE_COMPLETE_RETURN:-}" ] || "$REAL_GIT_FOR_TEST" -C "${!#}" checkout --quiet --detach [ -z "${FM_TEARDOWN_ORDER_LOG:-}" ] || printf 'treehouse-return %s\n' "$*" >> "$FM_TEARDOWN_ORDER_LOG" exit 0 SH @@ -4385,8 +4394,9 @@ test_secondmate_registry_updates_are_locked_and_literal() { } test_already_returned_worktree_with_landed_work_allows() { - local case_dir rc + local case_dir order_log rc case_dir=$(make_case already-returned-landed) + order_log="$case_dir/teardown-order.log" write_meta "$case_dir" no-mistakes ship wt_commit "$case_dir" "fix the thing" git -C "$case_dir/wt" push -q origin fm/task-x1 @@ -4394,13 +4404,16 @@ test_already_returned_worktree_with_landed_work_allows() { write_treehouse_unleased "$case_dir/wt" set +e - run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" + FM_FAKE_COMPLETE_RETURN=1 FM_TEARDOWN_ORDER_LOG="$order_log" \ + run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" rc=$? set -e [ "$rc" -eq 0 ] || fail "already-returned-landed: teardown should succeed when work is on a remote: $(cat "$case_dir/stderr")" assert_grep 'lease already cleared' "$case_dir/stderr" \ "already-returned-landed: teardown did not log the cleared-lease detection" + assert_grep 'treehouse-return return --force' "$order_log" \ + "already-returned-landed: teardown did not complete the Treehouse return" pass "already-returned worktree with landed work tears down cleanly" } @@ -4467,6 +4480,25 @@ test_duplicate_treehouse_entries_refuse_cleared_lease() { pass "duplicate Treehouse entries fail closed" } +test_missing_treehouse_entry_refuses_cleared_lease() { + local case_dir rc + case_dir=$(make_case already-returned-missing) + write_meta "$case_dir" no-mistakes ship + wt_commit "$case_dir" "fix the thing" + git -C "$case_dir/wt" push -q origin fm/task-x1 + write_treehouse_without_worktree "$case_dir/wt" + + set +e + run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" + rc=$? + set -e + + [ "$rc" -ne 0 ] || fail "already-returned-missing: missing lease entry was accepted" + assert_present "$case_dir/wt" "already-returned-missing: teardown removed the unproven worktree" + assert_present "$case_dir/state/task-x1.meta" "already-returned-missing: teardown removed task metadata" + pass "missing Treehouse entry fails closed" +} + test_fd_leak_under_low_ulimit() { local case_dir rc dir_count i case_dir=$(make_case fd-leak-ulimit) @@ -4502,6 +4534,7 @@ if [ "${FM_TEST_FOCUSED:-}" = already-returned-and-fd-leak ]; then test_already_returned_worktree_with_unlanded_work_refuses test_already_returned_worktree_cleanup_honors_checkout_lock test_duplicate_treehouse_entries_refuse_cleared_lease + test_missing_treehouse_entry_refuses_cleared_lease test_fd_leak_under_low_ulimit exit 0 fi From 66a7a763ddf9b14157aeda4ff6e173ed2cf63482 Mon Sep 17 00:00:00 2001 From: Dongkeun Lee Date: Mon, 27 Jul 2026 14:14:40 -0400 Subject: [PATCH 05/14] fix(tests): register missing-entry regression in default test suite The test_missing_treehouse_entry_refuses_cleared_lease case was registered in the focused block but omitted from the normal invocation list, so routine test runs skipped the fail-closed regression. Found by no-mistakes review round 4. --- tests/fm-teardown.test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/fm-teardown.test.sh b/tests/fm-teardown.test.sh index e0146cc38c..1f166a5b9a 100755 --- a/tests/fm-teardown.test.sh +++ b/tests/fm-teardown.test.sh @@ -4797,4 +4797,5 @@ test_already_returned_worktree_with_landed_work_allows test_already_returned_worktree_with_unlanded_work_refuses test_already_returned_worktree_cleanup_honors_checkout_lock test_duplicate_treehouse_entries_refuse_cleared_lease +test_missing_treehouse_entry_refuses_cleared_lease test_fd_leak_under_low_ulimit From be6fc47e134119a70b49a6468186960ab3e78ae8 Mon Sep 17 00:00:00 2001 From: Dongkeun Lee Date: Mon, 27 Jul 2026 22:14:48 -0400 Subject: [PATCH 06/14] fix(teardown): handle partial and completed returns gracefully MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend the already-returned worktree path to cover partial returns where the directory is gone or git state is broken after a failed manual return attempt: - treehouse_lease_is_cleared: accept entries with absent leased field (partial-return state where treehouse cleared the entry incompletely) - validate_teardown_target_identity: detect directory-gone + lease-cleared and directory-exists-but-git-broken + lease-cleared, both signaling TEARDOWN_WORKTREE_ALREADY_RETURNED instead of hard-failing - cleanup_already_returned_worktree_locked: fall back to HEAD when git rev-parse fails (partial return deregistered the worktree from git) - cleanup_already_returned_worktree_no_directory: new function for the directory-gone case — revalidates lease, cleans task branch from project - Call site restructured to route directory-gone already-returned through the no-directory path instead of skipping cleanup entirely No safety check is weakened: landed-work proofs, uncommitted-change detection, and fail-closed lease verification all run unconditionally. --- bin/fm-teardown.sh | 41 +++++++++++++++++++++++---- tests/fm-teardown.test.sh | 58 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 6 deletions(-) diff --git a/bin/fm-teardown.sh b/bin/fm-teardown.sh index a8275e7442..04ec2fa4a4 100755 --- a/bin/fm-teardown.sh +++ b/bin/fm-teardown.sh @@ -1057,7 +1057,8 @@ try: if len(matches) != 1: raise SystemExit(1) entry = matches[0] - if entry.get("leased") is not False: + leased = entry.get("leased") + if leased is not False and leased is not None: raise SystemExit(1) if entry.get("lease_holder") not in ("", None): raise SystemExit(1) @@ -1094,7 +1095,19 @@ validate_teardown_target_identity() { echo "error: teardown project metadata is not an exact inspectable repository root: ${PROJ:-}" >&2 return 1 } + if ! [ -d "$WT" ]; then + if treehouse_lease_is_cleared "$WT"; then + echo "teardown: worktree lease already cleared and directory gone (partial or completed return): $WT" >&2 + return "$TEARDOWN_WORKTREE_ALREADY_RETURNED" + fi + echo "error: teardown worktree directory is missing and lease state is indeterminate: ${WT:-}" >&2 + return 1 + fi worktree_root=$(exact_git_worktree_root "$WT") || { + if treehouse_lease_is_cleared "$WT"; then + echo "teardown: worktree lease already cleared (worktree may be a partial-return remnant): $WT" >&2 + return "$TEARDOWN_WORKTREE_ALREADY_RETURNED" + fi echo "error: teardown worktree metadata is not an exact inspectable repository root: ${WT:-}" >&2 return 1 } @@ -1383,7 +1396,7 @@ cleanup_already_returned_worktree_locked() { echo "error: worktree lease changed during final safety checks; retaining $WT" >&2 return 1 } - branch=$(git -C "$WT" rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1 + branch=$(git -C "$WT" rev-parse --abbrev-ref HEAD 2>/dev/null) || branch=HEAD validate_removal_tree_boundaries "$WT" "worktree" || return 1 if out=$(fm_checkout_treehouse_return_locked "$WT" "$CHECKOUT_LOCK_ROOT" "$PROJ" 2>&1); then [ -n "$out" ] && printf '%s\n' "$out" @@ -1400,6 +1413,18 @@ cleanup_already_returned_worktree_locked() { cleanup_recovered_worktree "$branch" "$WT" "$PROJ" } +cleanup_already_returned_worktree_no_directory() { + treehouse_lease_is_cleared "$WT" || { + echo "error: worktree lease is no longer provably cleared after directory-gone detection; retaining metadata" >&2 + return 1 + } + local branch + branch=$(git -C "$PROJ" for-each-ref --format='%(refname:short)' "refs/heads/fm/$ID" 2>/dev/null) + if [ -n "$branch" ]; then + git -C "$PROJ" branch -D "$branch" >/dev/null 2>&1 || true + fi +} + validate_worktree_teardown_safety() { local dirty_raw dirty unpushed_raw unpushed DEFAULT unmerged_raw unmerged branch stash_list [ -d "$WT" ] || return 0 @@ -4202,11 +4227,15 @@ if [ "$BACKEND" = orca ] && [ "$KIND" != secondmate ]; then ORCA_PATH_MATCH_VERIFIED=1 fi fm_checkout_lock_run "$WT" "$CHECKOUT_LOCK_ROOT" remove_orca_worktree_locked || exit 1 -elif [ -d "$WT" ] && [ "$KIND" != secondmate ]; then +elif [ "$KIND" != secondmate ]; then if [ "$WORKTREE_ALREADY_RETURNED" = 1 ]; then - fm_checkout_lock_run "$WT" "$CHECKOUT_LOCK_ROOT" \ - cleanup_already_returned_worktree_locked || exit 1 - else + if [ -d "$WT" ]; then + fm_checkout_lock_run "$WT" "$CHECKOUT_LOCK_ROOT" \ + cleanup_already_returned_worktree_locked || exit 1 + else + cleanup_already_returned_worktree_no_directory || exit 1 + fi + elif [ -d "$WT" ]; then post_lock_cleanup_check= if [ "$KIND" != secondmate ]; then post_lock_cleanup_check=validate_worktree_teardown_safety diff --git a/tests/fm-teardown.test.sh b/tests/fm-teardown.test.sh index 1f166a5b9a..7574da2edf 100755 --- a/tests/fm-teardown.test.sh +++ b/tests/fm-teardown.test.sh @@ -4499,6 +4499,60 @@ test_missing_treehouse_entry_refuses_cleared_lease() { pass "missing Treehouse entry fails closed" } +test_already_returned_directory_gone_cleans_metadata() { + local case_dir rc wt_path + case_dir=$(make_case already-returned-directorygone) + write_meta "$case_dir" no-mistakes ship + wt_commit "$case_dir" "fix the thing" + git -C "$case_dir/wt" push -q origin fm/task-x1 + wt_path=$(cd "$case_dir/wt" && pwd -P) + + write_treehouse_unleased "$case_dir/wt" + + git -C "$case_dir/project" worktree remove --force "$case_dir/wt" 2>/dev/null || true + rm -rf "$case_dir/wt" + + set +e + run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" + rc=$? + set -e + + [ "$rc" -eq 0 ] || fail "already-returned-directorygone: teardown should succeed when work is landed and directory is gone: $(cat "$case_dir/stderr")" + assert_grep 'lease already cleared' "$case_dir/stderr" \ + "already-returned-directorygone: teardown did not log the cleared-lease detection" + [ ! -e "$case_dir/state/task-x1.meta" ] || fail "already-returned-directorygone: task metadata was not cleaned" + pass "already-returned worktree with directory gone cleans metadata" +} + +test_already_returned_absent_leased_field_is_cleared() { + local case_dir rc slot pool state + case_dir=$(make_case already-returned-absent-leased) + write_meta "$case_dir" no-mistakes ship + wt_commit "$case_dir" "fix the thing" + git -C "$case_dir/wt" push -q origin fm/task-x1 + + slot=$(cd "$(dirname "$case_dir/wt")" && pwd -P) + pool=$(cd "$(dirname "$slot")" && pwd -P) + state="$pool/treehouse-state.json" + python3 - "$state" "$(cd "$case_dir/wt" && pwd -P)" <<'PY' +import json, sys +state, path = sys.argv[1:] +with open(state, "w", encoding="utf-8") as stream: + json.dump({"worktrees": [{"name": "1", "path": path}]}, stream) +PY + + set +e + FM_FAKE_COMPLETE_RETURN=1 \ + run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" + rc=$? + set -e + + [ "$rc" -eq 0 ] || fail "already-returned-absent-leased: teardown should succeed when leased field is absent: $(cat "$case_dir/stderr")" + assert_grep 'lease already cleared' "$case_dir/stderr" \ + "already-returned-absent-leased: teardown did not detect the cleared lease" + pass "already-returned worktree with absent leased field is treated as cleared" +} + test_fd_leak_under_low_ulimit() { local case_dir rc dir_count i case_dir=$(make_case fd-leak-ulimit) @@ -4535,6 +4589,8 @@ if [ "${FM_TEST_FOCUSED:-}" = already-returned-and-fd-leak ]; then test_already_returned_worktree_cleanup_honors_checkout_lock test_duplicate_treehouse_entries_refuse_cleared_lease test_missing_treehouse_entry_refuses_cleared_lease + test_already_returned_directory_gone_cleans_metadata + test_already_returned_absent_leased_field_is_cleared test_fd_leak_under_low_ulimit exit 0 fi @@ -4798,4 +4854,6 @@ test_already_returned_worktree_with_unlanded_work_refuses test_already_returned_worktree_cleanup_honors_checkout_lock test_duplicate_treehouse_entries_refuse_cleared_lease test_missing_treehouse_entry_refuses_cleared_lease +test_already_returned_directory_gone_cleans_metadata +test_already_returned_absent_leased_field_is_cleared test_fd_leak_under_low_ulimit From 75bf1e144a6173c787bff3b476f89e09a09a3f7b Mon Sep 17 00:00:00 2001 From: Dongkeun Lee Date: Mon, 27 Jul 2026 22:32:18 -0400 Subject: [PATCH 07/14] no-mistakes(review): Fail closed on ambiguous teardown recovery state --- bin/fm-teardown.sh | 36 ++++++++++++++++++++++++++++---- tests/fm-teardown.test.sh | 44 +++++++++++++++++++++++++++++++-------- 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/bin/fm-teardown.sh b/bin/fm-teardown.sh index 04ec2fa4a4..bff1d81a36 100755 --- a/bin/fm-teardown.sh +++ b/bin/fm-teardown.sh @@ -1057,8 +1057,7 @@ try: if len(matches) != 1: raise SystemExit(1) entry = matches[0] - leased = entry.get("leased") - if leased is not False and leased is not None: + if entry.get("leased") is not False: raise SystemExit(1) if entry.get("lease_holder") not in ("", None): raise SystemExit(1) @@ -1414,14 +1413,43 @@ cleanup_already_returned_worktree_locked() { } cleanup_already_returned_worktree_no_directory() { + local branch unpushed_raw unpushed DEFAULT treehouse_lease_is_cleared "$WT" || { echo "error: worktree lease is no longer provably cleared after directory-gone detection; retaining metadata" >&2 return 1 } - local branch branch=$(git -C "$PROJ" for-each-ref --format='%(refname:short)' "refs/heads/fm/$ID" 2>/dev/null) if [ -n "$branch" ]; then - git -C "$PROJ" branch -D "$branch" >/dev/null 2>&1 || true + if ! unpushed_raw=$(git -C "$PROJ" log --oneline "$branch" --not --remotes -- 2>/dev/null); then + echo "REFUSED: cannot inspect recovered branch $branch for commits not on a remote." >&2 + return 1 + fi + unpushed=$(printf '%s\n' "$unpushed_raw" | head -5) + if [ -n "$unpushed" ]; then + if [ "$MODE" = local-only ]; then + DEFAULT=$(default_branch) || { + echo "REFUSED: cannot determine default branch for recovered branch $branch." >&2 + return 1 + } + git -C "$PROJ" merge-base --is-ancestor "$branch" "$DEFAULT" 2>/dev/null || { + echo "REFUSED: recovered branch $branch has work not on any remote and not merged into $DEFAULT." >&2 + printf 'unpushed commits:\n%s\n' "$unpushed" >&2 + return 1 + } + else + echo "REFUSED: recovered branch $branch has work not on any remote." >&2 + printf 'unpushed commits:\n%s\n' "$unpushed" >&2 + return 1 + fi + fi + treehouse_lease_is_cleared "$WT" || { + echo "error: worktree lease changed during recovered branch safety checks; retaining metadata" >&2 + return 1 + } + git -C "$PROJ" branch -D "$branch" >/dev/null 2>&1 || { + echo "error: recovered worktree task branch could not be deleted: $branch" >&2 + return 1 + } fi } diff --git a/tests/fm-teardown.test.sh b/tests/fm-teardown.test.sh index 7574da2edf..5162e42f9c 100755 --- a/tests/fm-teardown.test.sh +++ b/tests/fm-teardown.test.sh @@ -4524,7 +4524,7 @@ test_already_returned_directory_gone_cleans_metadata() { pass "already-returned worktree with directory gone cleans metadata" } -test_already_returned_absent_leased_field_is_cleared() { +test_already_returned_absent_leased_field_refuses() { local case_dir rc slot pool state case_dir=$(make_case already-returned-absent-leased) write_meta "$case_dir" no-mistakes ship @@ -4542,15 +4542,39 @@ with open(state, "w", encoding="utf-8") as stream: PY set +e - FM_FAKE_COMPLETE_RETURN=1 \ - run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" + run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" rc=$? set -e - [ "$rc" -eq 0 ] || fail "already-returned-absent-leased: teardown should succeed when leased field is absent: $(cat "$case_dir/stderr")" - assert_grep 'lease already cleared' "$case_dir/stderr" \ - "already-returned-absent-leased: teardown did not detect the cleared lease" - pass "already-returned worktree with absent leased field is treated as cleared" + [ "$rc" -ne 0 ] || fail "already-returned-absent-leased: missing leased field was accepted" + assert_present "$case_dir/wt" "already-returned-absent-leased: teardown removed the worktree" + assert_present "$case_dir/state/task-x1.meta" "already-returned-absent-leased: teardown removed task metadata" + pass "already-returned worktree with absent leased field fails closed" +} + +test_already_returned_directory_gone_with_unpushed_work_refuses() { + local case_dir rc + case_dir=$(make_case already-returned-directorygone-unpushed) + write_meta "$case_dir" no-mistakes ship + wt_commit_file "$case_dir" unpushed.txt "content not on a remote" "add unpushed work" + write_treehouse_unleased "$case_dir/wt" + + git -C "$case_dir/project" worktree remove --force "$case_dir/wt" 2>/dev/null || true + rm -rf "$case_dir/wt" + + set +e + run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" + rc=$? + set -e + + [ "$rc" -ne 0 ] || fail "already-returned-directorygone-unpushed: teardown deleted an unpushed branch" + git -C "$case_dir/project" show-ref --verify --quiet refs/heads/fm/task-x1 \ + || fail "already-returned-directorygone-unpushed: task branch was deleted" + assert_present "$case_dir/state/task-x1.meta" \ + "already-returned-directorygone-unpushed: task metadata was removed" + assert_grep 'REFUSED: recovered branch fm/task-x1 has work not on any remote' "$case_dir/stderr" \ + "already-returned-directorygone-unpushed: teardown did not explain the refusal" + pass "directory-gone recovery preserves an unpushed task branch" } test_fd_leak_under_low_ulimit() { @@ -4590,7 +4614,8 @@ if [ "${FM_TEST_FOCUSED:-}" = already-returned-and-fd-leak ]; then test_duplicate_treehouse_entries_refuse_cleared_lease test_missing_treehouse_entry_refuses_cleared_lease test_already_returned_directory_gone_cleans_metadata - test_already_returned_absent_leased_field_is_cleared + test_already_returned_absent_leased_field_refuses + test_already_returned_directory_gone_with_unpushed_work_refuses test_fd_leak_under_low_ulimit exit 0 fi @@ -4855,5 +4880,6 @@ test_already_returned_worktree_cleanup_honors_checkout_lock test_duplicate_treehouse_entries_refuse_cleared_lease test_missing_treehouse_entry_refuses_cleared_lease test_already_returned_directory_gone_cleans_metadata -test_already_returned_absent_leased_field_is_cleared +test_already_returned_absent_leased_field_refuses +test_already_returned_directory_gone_with_unpushed_work_refuses test_fd_leak_under_low_ulimit From af47253096b81d83e0d076c43ed135978aff7147 Mon Sep 17 00:00:00 2001 From: Dongkeun Lee Date: Mon, 27 Jul 2026 22:42:51 -0400 Subject: [PATCH 08/14] no-mistakes(review): Fail closed on recovered branch lookup errors --- bin/fm-teardown.sh | 5 ++++- tests/fm-teardown.test.sh | 45 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/bin/fm-teardown.sh b/bin/fm-teardown.sh index bff1d81a36..849410bcc3 100755 --- a/bin/fm-teardown.sh +++ b/bin/fm-teardown.sh @@ -1418,7 +1418,10 @@ cleanup_already_returned_worktree_no_directory() { echo "error: worktree lease is no longer provably cleared after directory-gone detection; retaining metadata" >&2 return 1 } - branch=$(git -C "$PROJ" for-each-ref --format='%(refname:short)' "refs/heads/fm/$ID" 2>/dev/null) + if ! branch=$(git -C "$PROJ" for-each-ref --format='%(refname:short)' "refs/heads/fm/$ID" 2>/dev/null); then + echo "REFUSED: cannot inspect the recovered task branch for fm/$ID." >&2 + return 1 + fi if [ -n "$branch" ]; then if ! unpushed_raw=$(git -C "$PROJ" log --oneline "$branch" --not --remotes -- 2>/dev/null); then echo "REFUSED: cannot inspect recovered branch $branch for commits not on a remote." >&2 diff --git a/tests/fm-teardown.test.sh b/tests/fm-teardown.test.sh index 5162e42f9c..d401e1fdb8 100755 --- a/tests/fm-teardown.test.sh +++ b/tests/fm-teardown.test.sh @@ -740,6 +740,21 @@ SH chmod +x "$case_dir/fakebin/git" } +add_git_recovered_branch_lookup_failure() { + local case_dir=$1 + cat > "$case_dir/fakebin/git" <<'SH' +#!/usr/bin/env bash +if [ "${1:-}" = -C ] \ + && [ "${2:-}" = "${FM_FAKE_FOR_EACH_REF_PROJECT:?}" ] \ + && [ "${3:-}" = for-each-ref ]; then + echo "fatal: simulated recovered branch lookup failure" >&2 + exit 128 +fi +exec "${REAL_GIT_FOR_TEST:?}" "$@" +SH + chmod +x "$case_dir/fakebin/git" +} + # Run teardown with PATH mocking. Args: case_dir [extra args...] run_teardown() { local case_dir=$1; shift @@ -4577,6 +4592,34 @@ test_already_returned_directory_gone_with_unpushed_work_refuses() { pass "directory-gone recovery preserves an unpushed task branch" } +test_already_returned_directory_gone_branch_lookup_failure_refuses() { + local case_dir rc + case_dir=$(make_case already-returned-directorygone-lookup-failure) + write_meta "$case_dir" no-mistakes ship + wt_commit "$case_dir" "fix the thing" + git -C "$case_dir/wt" push -q origin fm/task-x1 + write_treehouse_unleased "$case_dir/wt" + + git -C "$case_dir/project" worktree remove --force "$case_dir/wt" 2>/dev/null || true + rm -rf "$case_dir/wt" + add_git_recovered_branch_lookup_failure "$case_dir" + + set +e + FM_FAKE_FOR_EACH_REF_PROJECT="$case_dir/project" \ + run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" + rc=$? + set -e + + [ "$rc" -ne 0 ] || fail "already-returned-directorygone-lookup-failure: teardown ignored branch lookup failure" + git -C "$case_dir/project" show-ref --verify --quiet refs/heads/fm/task-x1 \ + || fail "already-returned-directorygone-lookup-failure: task branch was deleted" + assert_present "$case_dir/state/task-x1.meta" \ + "already-returned-directorygone-lookup-failure: task metadata was removed" + assert_grep 'REFUSED: cannot inspect the recovered task branch for fm/task-x1' "$case_dir/stderr" \ + "already-returned-directorygone-lookup-failure: teardown did not explain the refusal" + pass "directory-gone recovery fails closed on branch lookup errors" +} + test_fd_leak_under_low_ulimit() { local case_dir rc dir_count i case_dir=$(make_case fd-leak-ulimit) @@ -4616,6 +4659,7 @@ if [ "${FM_TEST_FOCUSED:-}" = already-returned-and-fd-leak ]; then test_already_returned_directory_gone_cleans_metadata test_already_returned_absent_leased_field_refuses test_already_returned_directory_gone_with_unpushed_work_refuses + test_already_returned_directory_gone_branch_lookup_failure_refuses test_fd_leak_under_low_ulimit exit 0 fi @@ -4882,4 +4926,5 @@ test_missing_treehouse_entry_refuses_cleared_lease test_already_returned_directory_gone_cleans_metadata test_already_returned_absent_leased_field_refuses test_already_returned_directory_gone_with_unpushed_work_refuses +test_already_returned_directory_gone_branch_lookup_failure_refuses test_fd_leak_under_low_ulimit From 3573aacefd8a8b68eff576a31da030fc8bc418ae Mon Sep 17 00:00:00 2001 From: Dongkeun Lee Date: Mon, 27 Jul 2026 23:26:19 -0400 Subject: [PATCH 09/14] fix(tests): remove unused variable flagged by shellcheck Remove unused `wt_path` variable from test_already_returned_directory_gone_cleans_metadata to fix SC2034 (variable assigned but never used). --- tests/fm-teardown.test.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/fm-teardown.test.sh b/tests/fm-teardown.test.sh index d401e1fdb8..6af6d8040e 100755 --- a/tests/fm-teardown.test.sh +++ b/tests/fm-teardown.test.sh @@ -4515,12 +4515,11 @@ test_missing_treehouse_entry_refuses_cleared_lease() { } test_already_returned_directory_gone_cleans_metadata() { - local case_dir rc wt_path + local case_dir rc case_dir=$(make_case already-returned-directorygone) write_meta "$case_dir" no-mistakes ship wt_commit "$case_dir" "fix the thing" git -C "$case_dir/wt" push -q origin fm/task-x1 - wt_path=$(cd "$case_dir/wt" && pwd -P) write_treehouse_unleased "$case_dir/wt" From 4c414e59bc7bd000fa4b694f7f3f582772794ab6 Mon Sep 17 00:00:00 2001 From: Dongkeun Lee Date: Mon, 27 Jul 2026 23:30:19 -0400 Subject: [PATCH 10/14] no-mistakes(review): Serialize returned-worktree cleanup under checkout lock --- bin/fm-teardown.sh | 5 +++-- tests/fm-teardown.test.sh | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/bin/fm-teardown.sh b/bin/fm-teardown.sh index 849410bcc3..c48a468b88 100755 --- a/bin/fm-teardown.sh +++ b/bin/fm-teardown.sh @@ -1412,7 +1412,7 @@ cleanup_already_returned_worktree_locked() { cleanup_recovered_worktree "$branch" "$WT" "$PROJ" } -cleanup_already_returned_worktree_no_directory() { +cleanup_already_returned_worktree_no_directory_locked() { local branch unpushed_raw unpushed DEFAULT treehouse_lease_is_cleared "$WT" || { echo "error: worktree lease is no longer provably cleared after directory-gone detection; retaining metadata" >&2 @@ -4264,7 +4264,8 @@ elif [ "$KIND" != secondmate ]; then fm_checkout_lock_run "$WT" "$CHECKOUT_LOCK_ROOT" \ cleanup_already_returned_worktree_locked || exit 1 else - cleanup_already_returned_worktree_no_directory || exit 1 + fm_checkout_lock_run "$PROJ" "$CHECKOUT_LOCK_ROOT" \ + cleanup_already_returned_worktree_no_directory_locked || exit 1 fi elif [ -d "$WT" ]; then post_lock_cleanup_check= diff --git a/tests/fm-teardown.test.sh b/tests/fm-teardown.test.sh index 6af6d8040e..602d17ada1 100755 --- a/tests/fm-teardown.test.sh +++ b/tests/fm-teardown.test.sh @@ -4538,6 +4538,36 @@ test_already_returned_directory_gone_cleans_metadata() { pass "already-returned worktree with directory gone cleans metadata" } +test_already_returned_directory_gone_cleanup_honors_checkout_lock() { + local case_dir rc lock + case_dir=$(make_case already-returned-directorygone-lock) + write_meta "$case_dir" no-mistakes ship + wt_commit "$case_dir" "fix the thing" + git -C "$case_dir/wt" push -q origin fm/task-x1 + write_treehouse_unleased "$case_dir/wt" + + git -C "$case_dir/project" worktree remove --force "$case_dir/wt" 2>/dev/null || true + rm -rf "$case_dir/wt" + lock=$(checkout_lock_path "$case_dir/project" "$case_dir/checkout-locks") + mkdir -p "$lock" + printf '%s\n' "$$" > "$lock/pid" + + set +e + run_teardown "$case_dir" > "$case_dir/stdout" 2> "$case_dir/stderr" + rc=$? + set -e + + [ "$rc" -ne 0 ] || fail "already-returned-directorygone-lock: teardown bypassed the checkout lock" + git -C "$case_dir/project" show-ref --verify --quiet refs/heads/fm/task-x1 \ + || fail "already-returned-directorygone-lock: task branch was deleted" + assert_present "$case_dir/state/task-x1.meta" \ + "already-returned-directorygone-lock: task metadata was removed" + assert_grep "checkout mutation already running for $case_dir/project (pid $$)" \ + "$case_dir/stderr" "already-returned-directorygone-lock: teardown did not surface checkout lock contention" + rm -rf "$lock" + pass "directory-gone recovery holds the shared checkout lock" +} + test_already_returned_absent_leased_field_refuses() { local case_dir rc slot pool state case_dir=$(make_case already-returned-absent-leased) @@ -4656,6 +4686,7 @@ if [ "${FM_TEST_FOCUSED:-}" = already-returned-and-fd-leak ]; then test_duplicate_treehouse_entries_refuse_cleared_lease test_missing_treehouse_entry_refuses_cleared_lease test_already_returned_directory_gone_cleans_metadata + test_already_returned_directory_gone_cleanup_honors_checkout_lock test_already_returned_absent_leased_field_refuses test_already_returned_directory_gone_with_unpushed_work_refuses test_already_returned_directory_gone_branch_lookup_failure_refuses @@ -4923,6 +4954,7 @@ test_already_returned_worktree_cleanup_honors_checkout_lock test_duplicate_treehouse_entries_refuse_cleared_lease test_missing_treehouse_entry_refuses_cleared_lease test_already_returned_directory_gone_cleans_metadata +test_already_returned_directory_gone_cleanup_honors_checkout_lock test_already_returned_absent_leased_field_refuses test_already_returned_directory_gone_with_unpushed_work_refuses test_already_returned_directory_gone_branch_lookup_failure_refuses From e66b6887de5454b0493f2006e5906b735fdb9a24 Mon Sep 17 00:00:00 2001 From: Dongkeun Lee Date: Tue, 28 Jul 2026 12:04:11 -0400 Subject: [PATCH 11/14] no-mistakes(document): Document already-returned worktree teardown recovery --- AGENTS.md | 3 ++- bin/fm-teardown.sh | 8 ++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 0b6959e1e7..b5908157fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -541,7 +541,8 @@ bin/fm-teardown.sh ``` The script refuses if the worktree holds uncommitted changes or committed work that has not landed; treat a refusal as a stop-and-investigate, not an obstacle. -Teardown validates that the recorded project and worktree are exact roots with the expected repository registration, quiesces every ordinary task endpoint, and then runs the final non-destructive safety checks before any Treehouse return. +Teardown normally validates that the recorded project and worktree are exact roots with the expected repository registration, quiesces every ordinary task endpoint, and then runs the final non-destructive safety checks before any Treehouse return. +It can also finish bookkeeping for a provably landed worktree whose Treehouse lease was already cleared; `bin/fm-teardown.sh`'s header owns the fail-closed recovery contract. For a task whose metadata carries `report_required=1`, teardown also publishes the validated completion report before releasing the account lease or removing the worktree. A safety refusal after quiescence leaves the endpoint stopped while preserving all task state for repair and retry. `bin/fm-teardown.sh`'s header owns the full landed-work definition (remote-reachable, merged-PR-head containment for the squash-merge-then-delete-branch flow, content already in the default branch, local-only merges) and the `pr=` discovery fallback for merges that skipped `bin/fm-pr-check.sh`. diff --git a/bin/fm-teardown.sh b/bin/fm-teardown.sh index c48a468b88..fe50545c98 100755 --- a/bin/fm-teardown.sh +++ b/bin/fm-teardown.sh @@ -29,6 +29,14 @@ # Uncommitted changes are never landed. # Ordinary teardown first proves that metadata names the exact registered project, # worktree, and task lease, then quiesces the endpoint before its final safety checks. +# If Treehouse already cleared that exact pool entry's lease, teardown treats the +# worktree as a partial or completed external return only when the state has exactly +# one matching entry, leased=false, no lease holder, and destroying is not true. +# It then re-proves landed work under the checkout lock before completing the return +# or cleaning the returned worktree, branch, and task bookkeeping. A missing +# worktree directory is accepted only through the same cleared-lease proof and a +# recovered task branch whose landed state can still be proved; ambiguous lease +# state, branch lookup failure, and unlanded recovered work all retain task state. # Each locked Treehouse return repeats repository, lease, and landed-work checks # immediately before the destructive return command. # local-only projects additionally accept work merged into the local default From 94009cdf3da651c391fa8cfd96af86bd03353848 Mon Sep 17 00:00:00 2001 From: Dongkeun Lee Date: Tue, 28 Jul 2026 13:07:55 -0400 Subject: [PATCH 12/14] no-mistakes: apply CI fixes --- bin/fm-spawn.sh | 2 +- tests/fm-account-routing.test.sh | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bin/fm-spawn.sh b/bin/fm-spawn.sh index e66d231827..71fa6cd084 100755 --- a/bin/fm-spawn.sh +++ b/bin/fm-spawn.sh @@ -2701,7 +2701,7 @@ if [ "$KIND" = secondmate ]; then fi if [ "$ACCOUNT_EFFECTIVE_MODE" = enforce ]; then if ! secondmate_home_supports_account_routing "$PROJ_ABS"; then - echo "error: refusing account-routed secondmate launch for $PROJ_ABS: the home lacks Agent Fleet routing support. Fast-forward or otherwise reconcile the home to this Firstmate revision, run bin/fm-config-push.sh, and retry." >&2 + echo "error: refusing account-routed secondmate $ID launch for $PROJ_ABS: the home lacks Agent Fleet routing support. Fast-forward or otherwise reconcile the home to this Firstmate revision, run bin/fm-config-push.sh, and retry." >&2 exit 1 fi elif ! secondmate_home_supports_account_routing "$PROJ_ABS"; then diff --git a/tests/fm-account-routing.test.sh b/tests/fm-account-routing.test.sh index 8bb02b29a9..4c016b9437 100755 --- a/tests/fm-account-routing.test.sh +++ b/tests/fm-account-routing.test.sh @@ -2226,7 +2226,8 @@ test_enforced_secondmate_requires_routing_inheritance_and_capable_home() { status=$? [ "$status" -ne 0 ] || fail "enforced secondmate launched from a pre-Agent-Fleet home" assert_contains "$out" "$id" "capability refusal omitted the offending secondmate" - assert_contains "$out" "dirty working tree" "capability refusal did not stop at the freshness gate" + assert_contains "$out" "home lacks Agent Fleet routing support" \ + "capability refusal omitted the unsupported-home reason" assert_not_grep '^new-window ' "$TMUX_LOG" "capability refusal created an endpoint" pass "enforced secondmates require inherited routing policy and Agent Fleet-capable homes" } @@ -2269,9 +2270,9 @@ test_secondmate_routing_inheritance_is_authoritative_for_every_mode() { sm=$(cd "$sm" && pwd -P) out=$(FM_TEST_PANE_PATH="$sm" run_spawn "$id" "$sm" --secondmate) status=$? - [ "$status" -ne 0 ] || fail "off secondmate launched from a dirty, capability-drifted home" - assert_contains "$out" "dirty working tree" "off capability drift did not stop at the freshness gate" - assert_not_grep '^new-window ' "$TMUX_LOG" "off capability drift created an endpoint" + [ "$status" -eq 0 ] || fail "off secondmate refused a clean home that does not need routing capability" + assert_contains "$out" "lacks Agent Fleet routing support; launching because account routing is off" \ + "off capability warning omitted the explicit routing policy" pass "secondmate launches require authoritative routing policy in every mode" } From 082a6f51b9d4c733c1f137c57d3ea6fbc7ec07ff Mon Sep 17 00:00:00 2001 From: Dongkeun Lee Date: Tue, 28 Jul 2026 13:39:54 -0400 Subject: [PATCH 13/14] no-mistakes: apply CI fixes --- tests/fm-backend-herdr.test.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/fm-backend-herdr.test.sh b/tests/fm-backend-herdr.test.sh index 69351fd2f5..7210d03d98 100755 --- a/tests/fm-backend-herdr.test.sh +++ b/tests/fm-backend-herdr.test.sh @@ -2627,7 +2627,7 @@ test_capture_calls_pane_read() { # fetch bound; the adapter then trims to the caller's requested 250 lines # locally, so all 3 fake lines survive. out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_capture default:w1:p2 250' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_capture default:w1:p2 250' "$ROOT" ) [ "$out" = $'line one\nline two\nline three' ] || fail "capture did not pass through pane read output, got '$out'" assert_contains "$(cat "$log")" "HERDR_SESSION=default"$'\x1f''pane'$'\x1f''read'$'\x1f''w1:p2'$'\x1f''--source'$'\x1f''recent'$'\x1f''--lines'$'\x1f''250' \ "capture did not call pane read with the right pane id and line bound" @@ -2644,7 +2644,7 @@ test_capture_works_around_small_lines_bug() { printf 'a\nb\nc\nd\ne\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_capture default:w1:p2 2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_capture default:w1:p2 2' "$ROOT" ) [ "$out" = $'d\ne' ] || fail "a small --lines request should still return the last N lines (trimmed locally), got '$out'" assert_contains "$(cat "$log")" $'\x1f''--lines'$'\x1f''200' \ "capture should request a generous fetch (>=200), never the caller's small N, from herdr's own --lines flag" @@ -2657,7 +2657,7 @@ test_capture_preserves_pane_read_failure() { printf '1\n' > "$resp/1.exit" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_capture default:w1:p2 2' "$ROOT" 2>&1 ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_capture default:w1:p2 2' "$ROOT" 2>&1 ) status=$? [ "$status" -ne 0 ] || fail "capture should fail when pane read fails, got output '$out'" assert_contains "$(cat "$log")" "HERDR_SESSION=default"$'\x1f''status'$'\x1f''--json' \ From d41ffa06ba7b823b3cab7613c4ac129f8bbb1949 Mon Sep 17 00:00:00 2001 From: Dongkeun Lee Date: Tue, 28 Jul 2026 14:31:47 -0400 Subject: [PATCH 14/14] no-mistakes: apply CI fixes --- tests/fm-backend-herdr.test.sh | 91 ++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/tests/fm-backend-herdr.test.sh b/tests/fm-backend-herdr.test.sh index 7210d03d98..fe777b242e 100755 --- a/tests/fm-backend-herdr.test.sh +++ b/tests/fm-backend-herdr.test.sh @@ -81,6 +81,21 @@ SH printf '%s\n' "$fb" } +write_adapter_owned_certificate() { # + local lock_root=$1 session=$2 + mkdir -p "$lock_root" + chmod 700 "$lock_root" + FM_BACKEND_HERDR_SERVER_LOCK_ROOT="$lock_root" bash -c ' + . "$0/bin/backends/herdr.sh" + key=$(fm_backend_herdr_server_lock_key "$2") || exit 1 + certificate=$(fm_backend_herdr_server_legacy_env_certificate_path "$2") || exit 1 + start=$(fm_backend_herdr_process_start "$1") || exit 1 + printf "firstmate-herdr-closed-env-v1\n%s\n%s\n%s\n" \ + "$key" "$1" "$start" > "$certificate" + chmod 600 "$certificate" + ' "$ROOT" "$$" "$session" +} + # make_herdr_statefake: a STATEFUL `herdr` stub that models the parts of herdr's # real container behavior the workspace-leak fix (and the default-tab-prune # safety fix) depend on, so a full spawn->teardown cycle can be replayed @@ -2672,7 +2687,7 @@ test_send_key_normalizes_and_targets_pane() { dir="$TMP_ROOT/sendkey"; mkdir -p "$dir/responses"; log="$dir/log"; resp="$dir/responses"; : > "$log" fb=$(make_herdr_fakebin "$dir") PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_send_key default:w1:p2 Escape' "$ROOT" + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_send_key default:w1:p2 Escape' "$ROOT" expect_code 0 $? "send_key should succeed" assert_contains "$(cat "$log")" $'\x1f''pane'$'\x1f''send-keys'$'\x1f''w1:p2'$'\x1f''escape' "send_key did not normalize Escape to escape" pass "fm_backend_herdr_send_key: normalizes the key and targets the right pane" @@ -2684,7 +2699,7 @@ test_kill_is_best_effort() { printf '1\n' > "$resp/1.exit" fb=$(make_herdr_fakebin "$dir") PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_kill default:w1:p2' "$ROOT" + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_kill default:w1:p2' "$ROOT" expect_code 0 $? "kill must be best-effort (never fail even when the pane close call itself fails)" assert_contains "$(cat "$log")" $'\x1f''pane'$'\x1f''close'$'\x1f''w1:p2' "kill did not call pane close on the right pane" pass "fm_backend_herdr_kill: calls pane close and stays best-effort on failure" @@ -2886,7 +2901,7 @@ test_current_path_reads_cwd() { printf '{"result":{"pane":{"cwd":"/tmp/pane-creation-dir","foreground_cwd":"/tmp/fake-worktree"}}}\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_current_path default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_current_path default:w1:p2' "$ROOT" ) [ "$out" = "/tmp/fake-worktree" ] || fail "current_path should read foreground_cwd (the live process), not the frozen creation-time cwd, got '$out'" assert_contains "$(cat "$log")" $'\x1f''pane'$'\x1f''get'$'\x1f''w1:p2' "current_path did not call pane get" pass "fm_backend_herdr_current_path: reads pane foreground_cwd (the live running process), not the frozen creation-time cwd" @@ -2900,7 +2915,7 @@ test_busy_state_working_maps_to_busy() { printf '{"result":{"agent":{"agent_status":"working"}}}\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_busy_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_busy_state default:w1:p2' "$ROOT" ) [ "$out" = busy ] || fail "agent_status=working should map to busy, got '$out'" assert_contains "$(cat "$log")" $'\x1f''agent'$'\x1f''get'$'\x1f''w1:p2' "busy_state did not call agent get" pass "fm_backend_herdr_busy_state: working -> busy" @@ -2912,14 +2927,14 @@ test_busy_state_done_and_blocked_map_to_idle() { printf '{"result":{"agent":{"agent_status":"done"}}}\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_busy_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_busy_state default:w1:p2' "$ROOT" ) [ "$out" = idle ] || fail "agent_status=done should map to idle, got '$out'" dir="$TMP_ROOT/busy-blocked"; mkdir -p "$dir/responses"; log="$dir/log"; resp="$dir/responses"; : > "$log" printf '{"result":{"agent":{"agent_status":"blocked"}}}\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_busy_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_busy_state default:w1:p2' "$ROOT" ) [ "$out" = idle ] || fail "agent_status=blocked should map to idle (stuck waiting on the human, not grinding), got '$out'" pass "fm_backend_herdr_busy_state: done -> idle, blocked -> idle (surfaced like a stale pane, not suppressed as busy)" } @@ -2930,7 +2945,7 @@ test_busy_state_unknown_on_no_agent() { printf '1\n' > "$resp/1.exit" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_busy_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_busy_state default:w1:p2' "$ROOT" ) [ "$out" = unknown ] || fail "a failed agent get should report unknown (the fallback-to-regex cue), got '$out'" pass "fm_backend_herdr_busy_state: unparseable/absent agent state reports unknown, the regex-fallback cue" } @@ -2943,7 +2958,7 @@ test_composer_state_bare_prompt_is_empty() { printf ' ╭────────────────────────╮\n │ ❯ │\n ╰──────── Composer ─────╯\n\n Shift+Tab:mode\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) [ "$out" = empty ] || fail "a bare prompt glyph should read as empty, got '$out'" pass "fm_backend_herdr_composer_state: a bare '❯' composer row reads empty" } @@ -2954,7 +2969,7 @@ test_composer_state_ghost_placeholder_is_empty() { printf ' ╭────────────────────────╮\n │ ❯ Type a message... │\n ╰──────── Composer ─────╯\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) [ "$out" = empty ] || fail "the known ghost placeholder 'Type a message...' should read as empty, got '$out'" pass "fm_backend_herdr_composer_state: the ghost placeholder text reads empty, not pending" } @@ -2965,7 +2980,7 @@ test_composer_state_real_text_is_pending() { printf ' ╭────────────────────────╮\n │ ❯ hello captain │\n ╰──────── Composer ─────╯\n\n Enter:send\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) [ "$out" = pending ] || fail "real unsubmitted text should read as pending, got '$out'" pass "fm_backend_herdr_composer_state: real composer text reads pending" } @@ -2984,7 +2999,7 @@ test_composer_state_popup_placeholder_fill_is_pending() { printf ' ╭──────────────────────────────────────╮\n │ ❯ /compact compaction instructions │\n ╰──────────────── Composer ─────────────╯\n\n Enter:send\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) [ "$out" = pending ] || fail "a popup-close-with-placeholder-fill must still read as pending (not yet submitted), got '$out'" pass "fm_backend_herdr_composer_state: a slash-command popup's argument-hint placeholder still reads pending (the incident fix)" } @@ -2995,7 +3010,7 @@ test_composer_state_unknown_on_capture_failure() { printf '1\n' > "$resp/1.exit" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) status=$? [ "$status" -eq 0 ] || fail "composer_state should not itself fail the caller" [ "$out" = unknown ] || fail "an unreadable pane should read as unknown, got '$out'" @@ -3012,7 +3027,7 @@ test_composer_state_unknown_when_no_composer_row_found() { fb=$(make_herdr_fakebin "$dir") for glyph in '>' '$' '%' '#'; do out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) [ "$out" = unknown ] || fail "a bare shell prompt '$glyph' should read as unknown, got '$out'" done pass "fm_backend_herdr_composer_state: reports unknown for bare shell prompts with no composer row" @@ -3037,7 +3052,7 @@ test_composer_state_claude_unbordered_prompt_is_empty() { printf ' 20\n 21\n\n\xe2\x9c\xbb Worked for 2s\n\n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n\xe2\x9d\xaf\n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n Opus 4.8 (1M context) \xe2\x96\x8d 3%%\n \xe2\x86\x90 for agents\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) [ "$out" = empty ] || fail "a genuinely idle, unbordered real-claude '❯' prompt row (no border glyph anywhere in view) should read empty, got '$out' (regression: this used to read 'unknown' forever, which is exactly what broke escalate_flush's buffer-clear)" pass "fm_backend_herdr_composer_state: a real-claude unbordered '❯' prompt row (no border box in view) reads empty" } @@ -3048,7 +3063,7 @@ test_composer_state_claude_unbordered_prompt_is_pending() { printf ' 20\n 21\n\n\xe2\x9c\xbb Worked for 2s\n\n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n\xe2\x9d\xaf hello there this is a test message\n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) [ "$out" = pending ] || fail "real unsubmitted text in an unbordered real-claude prompt row should read pending, got '$out'" pass "fm_backend_herdr_composer_state: a real-claude unbordered '❯ ' prompt row reads pending" } @@ -3069,7 +3084,7 @@ test_composer_state_bare_prompt_below_stale_bordered_banner_wins() { printf '\xe2\x95\xad\xe2\x94\x80 Claude Code \xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x95\xae\n\xe2\x94\x82 Welcome back Kun! \xe2\x94\x82\n\xe2\x94\x82 \xe2\x94\x82\n\xe2\x95\xb0\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x95\xaf\n\n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n\xe2\x9d\xaf still typing captain\n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) [ "$out" = pending ] || fail "the live unbordered prompt row below a stale bordered banner must win (pending, real text present), got '$out'" pass "fm_backend_herdr_composer_state: a live unbordered prompt row below a stale bordered decorative box still wins (not misread as the box's own row)" } @@ -3092,7 +3107,7 @@ test_composer_state_claude_dim_prompt_suggestion_ghost_is_empty() { printf '\xe2\x9c\xbb Brewed for 2m 40s\n\n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n\xe2\x9d\xaf \x1b[0m\x1b[2mwhat did the wheelhouse healing verification find?\x1b[0m\n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n Fable 5 80%%\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p3' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p3' "$ROOT" ) [ "$out" = empty ] || fail "the overnight shape - claude's SGR-2 dim prompt-suggestion ghost after a bare '❯' - must read empty, got '$out' (regression: this false-pending wedged away-mode injection all night)" pass "fm_backend_herdr_composer_state: claude's dim prompt-suggestion ghost (the overnight wedge shape) reads empty" } @@ -3106,7 +3121,7 @@ test_composer_state_claude_dim_ghost_row_with_real_text_is_pending() { printf '\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n\xe2\x9d\xaf land pr 416 now\n\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\n Fable 5 80%%\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p3' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p3' "$ROOT" ) [ "$out" = pending ] || fail "real normal-intensity text after '❯' must still read pending, got '$out'" pass "fm_backend_herdr_composer_state: real typed text on the same claude prompt row still reads pending" } @@ -3123,7 +3138,7 @@ test_composer_state_grok_dark_truecolor_placeholder_is_empty() { printf ' \x1b[38;2;86;82;110m\xe2\x95\xad\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x95\xae\x1b[39m\n \x1b[38;2;86;82;110m\xe2\x94\x82\x1b[38;2;224;222;244m \xe2\x9d\xaf \x1b[38;2;50;47;70mType a message...\x1b[38;2;86;82;110m \xe2\x94\x82\x1b[39m\n \x1b[38;2;86;82;110m\xe2\x95\xb0\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x95\xaf\x1b[39m\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) [ "$out" = empty ] || fail "a grok bordered composer whose only content is a dark-truecolor placeholder must read empty, got '$out'" pass "fm_backend_herdr_composer_state: grok's dark-truecolor placeholder (the TRUECOLOR gap) reads empty" } @@ -3135,7 +3150,7 @@ test_composer_state_grok_bright_truecolor_real_text_is_pending() { printf ' \x1b[38;2;86;82;110m\xe2\x94\x82\x1b[38;2;224;222;244m \xe2\x9d\xaf fix the login bug \x1b[38;2;86;82;110m\xe2\x94\x82\x1b[39m\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) [ "$out" = pending ] || fail "real bright typed text in a grok bordered composer must read pending, got '$out'" pass "fm_backend_herdr_composer_state: grok's real bright typed input still reads pending" } @@ -3146,7 +3161,7 @@ test_composer_state_codex_bare_prompt_glyph_is_empty() { printf '\xe2\x80\xa2 You have 2 usage limit resets available.\n\n\xe2\x80\xba\n\n gpt-5.5 xhigh \xc2\xb7 Context 100%% left\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) [ "$out" = empty ] || fail "a bare '›' (codex) prompt glyph with no trailing text should read empty, got '$out'" pass "fm_backend_herdr_composer_state: a real-codex unbordered '›' prompt row reads empty" } @@ -3157,7 +3172,7 @@ test_composer_state_codex_faint_suggestion_is_empty() { printf '\xe2\x80\xa2 You have 2 usage limit resets available. Run /usage\nto use one.\n\n\x1b[0m\x1b[1m\xe2\x80\xba \x1b[0m\x1b[2mFind and fix a bug in @filename\x1b[0m\n\n gpt-5.5 xhigh \xc2\xb7 Context 100%% left\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) [ "$out" = empty ] || fail "a faint real-codex ghost suggestion should read empty, not pending, got '$out'" pass "fm_backend_herdr_composer_state: a faint real-codex ghost suggestion reads empty" } @@ -3168,7 +3183,7 @@ test_composer_state_codex_non_faint_same_text_is_pending() { printf '\xe2\x80\xa2 You have 2 usage limit resets available. Run /usage\nto use one.\n\n\x1b[0m\x1b[1m\xe2\x80\xba \x1b[0mFind and fix a bug in @filename\n\n gpt-5.5 xhigh \xc2\xb7 Context 100%% left\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) [ "$out" = pending ] || fail "the same words without faint styling should still protect real typed input, got '$out'" pass "fm_backend_herdr_composer_state: non-faint codex prompt text still reads pending" } @@ -3239,7 +3254,7 @@ test_send_text_submit_applies_herdr_minimum_confirm_budget() { printf '{"result":{"agent":{"agent_status":"working"}}}\n' > "$resp/9.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" FM_SLEEP_LOG="$sleep_log" FM_BACKEND_HERDR_SUBMIT_POLLS=6 FM_BACKEND_HERDR_SUBMIT_MIN_SLEEP=0.6 \ - bash -c '. "$0/bin/backends/herdr.sh"; sleep() { printf "sleep:%s\n" "$1" >> "$FM_SLEEP_LOG"; }; fm_backend_herdr_send_text_submit default:w1:p2 "hello captain" 1 0.4 0' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; sleep() { printf "sleep:%s\n" "$1" >> "$FM_SLEEP_LOG"; }; fm_backend_herdr_send_text_submit default:w1:p2 "hello captain" 1 0.4 0' "$ROOT" ) [ "$out" = empty ] || fail "send_text_submit should catch a slow-but-valid transition inside the herdr minimum budget, got '$out'" sleeps=$(grep -c '^sleep:0.1200$' "$sleep_log") [ "$sleeps" -eq 5 ] || fail "a 0.4s caller budget should be expanded to five 0.1200s sleeps across the 0.6s herdr floor, got $sleeps; log: $(cat "$sleep_log")" @@ -3304,7 +3319,7 @@ test_send_text_submit_detects_landed_send() { printf '{"result":{"agent":{"agent_status":"working"}}}\n' > "$resp/4.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" FM_BACKEND_HERDR_SUBMIT_POLLS=1 \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_send_text_submit default:w1:p2 "hello captain" 3 0.01 0.01' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_send_text_submit default:w1:p2 "hello captain" 3 0.01 0.01' "$ROOT" ) [ "$out" = empty ] || fail "send_text_submit should report empty (submitted) once agent_status reports working, got '$out'" assert_contains "$(cat "$log")" $'\x1f''agent'$'\x1f''send'$'\x1f''w1:p2'$'\x1f''hello captain' \ "send_text_submit did not address the registered agent with the literal text first" @@ -3324,7 +3339,7 @@ test_send_text_submit_detects_swallowed_enter() { printf '{"result":{"agent":{"agent_status":"idle"}}}\n' > "$resp/6.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" FM_BACKEND_HERDR_SUBMIT_POLLS=1 \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_send_text_submit default:w1:p2 "hello captain" 2 0.01 0.01' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_send_text_submit default:w1:p2 "hello captain" 2 0.01 0.01' "$ROOT" ) [ "$out" = pending ] || fail "send_text_submit should report pending once retries are exhausted with agent_status never going busy, got '$out'" pass "fm_backend_herdr_send_text_submit: reports 'pending' when agent_status never reports working after retried Enters (swallowed)" } @@ -3349,7 +3364,7 @@ test_send_text_submit_popup_autocomplete_requires_second_enter() { printf '{"result":{"agent":{"agent_status":"working"}}}\n' > "$resp/6.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" FM_BACKEND_HERDR_SUBMIT_POLLS=1 \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_send_text_submit default:w1:p2 "/compact" 3 0.01 1.2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_send_text_submit default:w1:p2 "/compact" 3 0.01 1.2' "$ROOT" ) [ "$out" = empty ] || fail "send_text_submit should eventually report empty once the SECOND Enter actually starts a turn, got '$out'" enter_count=$(grep -c $'\x1f''pane'$'\x1f''send-keys'$'\x1f''w1:p2'$'\x1f''enter' "$log") [ "$enter_count" -eq 2 ] || fail "send_text_submit must send a SECOND Enter after the popup-placeholder fill's agent_status still reads idle, got $enter_count Enter(s)" @@ -3364,7 +3379,7 @@ test_send_text_submit_confirms_blocked_after_enter() { printf '{"result":{"agent":{"agent_status":"blocked"}}}\n' > "$resp/4.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" FM_BACKEND_HERDR_SUBMIT_POLLS=1 \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_send_text_submit default:w1:p2 "needs approval" 3 0.01 0.01' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_send_text_submit default:w1:p2 "needs approval" 3 0.01 0.01' "$ROOT" ) [ "$out" = empty ] || fail "send_text_submit should treat a blocked state after Enter as a confirmed delivered prompt, got '$out'" enter_count=$(grep -c $'\x1f''pane'$'\x1f''send-keys'$'\x1f''w1:p2'$'\x1f''enter' "$log") [ "$enter_count" -eq 1 ] || fail "blocked after Enter must not provoke a retry into the prompt, sent $enter_count Enter(s)" @@ -3380,7 +3395,7 @@ test_send_text_submit_preexisting_working_does_not_false_confirm_swallowed_enter printf ' \xe2\x9d\xaf hello captain\n' > "$resp/6.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_send_text_submit default:w1:p2 "hello captain" 2 0.01 0.01' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_send_text_submit default:w1:p2 "hello captain" 2 0.01 0.01' "$ROOT" ) [ "$out" = pending ] || fail "send_text_submit must not accept preexisting working as proof that this Enter landed, got '$out'" enter_count=$(grep -c $'\x1f''pane'$'\x1f''send-keys'$'\x1f''w1:p2'$'\x1f''enter' "$log") [ "$enter_count" -eq 2 ] || fail "preexisting-working swallowed Enter should retry Enter up to the configured count, sent $enter_count Enter(s)" @@ -3400,7 +3415,7 @@ test_send_text_submit_confirms_despite_codex_idle_tip_composer() { printf '{"result":{"agent":{"agent_status":"working"}}}\n' > "$resp/4.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" FM_BACKEND_HERDR_SUBMIT_POLLS=1 \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_send_text_submit default:w1:p2 "reply with just OK" 3 0.01 0.01' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_send_text_submit default:w1:p2 "reply with just OK" 3 0.01 0.01' "$ROOT" ) [ "$out" = empty ] || fail "send_text_submit should confirm via agent_status alone even for a harness whose idle composer shows dynamic tip text, got '$out'" [ "$(grep -c $'\x1f''pane'$'\x1f''read' "$log")" -eq 0 ] || fail "send_text_submit must never call 'pane read' - a codex-style dynamic idle-tip composer can never mislead a confirmation path that does not read it" pass "fm_backend_herdr_send_text_submit: confirms submission via native agent-state alone, immune to a codex-style dynamic idle-tip composer that would have misread as 'pending' under the old composer-based confirmation" @@ -3417,7 +3432,7 @@ test_composer_state_codex_dynamic_idle_tip_reads_empty_when_faint() { printf '\xe2\x80\xa2 OK\n\n\n\x1b[0m\x1b[1m\xe2\x80\xba \x1b[0m\x1b[2mSummarize recent commits\x1b[0m\n\n gpt-5.5 xhigh \xc2\xb7 Context 97%% left \xc2\xb7 /private/tmp \xc2\xb7 2\xe2\x80\xa6\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_composer_state default:w1:p2' "$ROOT" ) [ "$out" = empty ] || fail "a faint real-codex dynamic idle-tip row should read empty, got '$out'" pass "fm_backend_herdr_composer_state: a faint real-codex dynamic idle-tip composer row reads empty" } @@ -3434,7 +3449,7 @@ test_composer_state_guard_still_refuses_real_pending_text_after_submit_confirmat printf ' \xe2\x9d\xaf hello there this is a test message\n' > "$resp/1.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ - bash -c '. "$0/bin/fm-backend.sh"; fm_backend_composer_state herdr default:w1:p2' "$ROOT" ) + bash -c '. "$0/bin/fm-backend.sh"; . "$0/bin/backends/herdr.sh"; fm_backend_source() { return 0; }; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_composer_state herdr default:w1:p2' "$ROOT" ) [ "$out" = pending ] || fail "the pre-injection empty-box guard must still refuse real unsubmitted composer text after this change, got '$out'" pass "fm_backend_composer_state (herdr): the pre-injection empty-box guard still refuses a genuinely non-empty composer, unaffected by the submit-confirmation change" } @@ -3454,7 +3469,7 @@ test_send_text_submit_slow_transition_within_one_enter_needs_no_extra_enter() { printf '{"result":{"agent":{"agent_status":"working"}}}\n' > "$resp/6.out" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" FM_BACKEND_HERDR_SUBMIT_POLLS=3 \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_send_text_submit default:w1:p2 "hello captain" 3 0.03 0.01' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_send_text_submit default:w1:p2 "hello captain" 3 0.03 0.01' "$ROOT" ) [ "$out" = empty ] || fail "send_text_submit should confirm once a later sample within the SAME Enter attempt observes working, got '$out'" enter_count=$(grep -c $'\x1f''pane'$'\x1f''send-keys'$'\x1f''w1:p2'$'\x1f''enter' "$log") [ "$enter_count" -eq 1 ] || fail "a slow (but within-budget) transition must not provoke a needless extra Enter, sent $enter_count Enter(s)" @@ -3469,7 +3484,7 @@ test_send_text_submit_send_failed() { printf '1\n' > "$resp/2.exit" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" FM_BACKEND_HERDR_SUBMIT_POLLS=1 \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_send_text_submit default:w1:p2 "x" 2 0.01 0.01' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_send_text_submit default:w1:p2 "x" 2 0.01 0.01' "$ROOT" ) [ "$out" = send-failed ] || fail "send_text_submit should report send-failed when the literal send itself fails, got '$out'" pass "fm_backend_herdr_send_text_submit: reports 'send-failed' when native agent send and the pane-send fallback both error" } @@ -3481,7 +3496,7 @@ test_send_text_submit_unknown_on_capture_failure() { printf '1\n' > "$resp/4.exit" fb=$(make_herdr_fakebin "$dir") out=$( PATH="$fb:$PATH" FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" FM_BACKEND_HERDR_SUBMIT_POLLS=1 \ - bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_send_text_submit default:w1:p2 "x" 2 0.01 0.01' "$ROOT" ) + bash -c '. "$0/bin/backends/herdr.sh"; fm_backend_herdr_server_adapter_owned() { return 0; }; fm_backend_herdr_send_text_submit default:w1:p2 "x" 2 0.01 0.01' "$ROOT" ) [ "$out" = unknown ] || fail "send_text_submit should report unknown when the post-Enter agent-get read fails, got '$out'" enter_count=$(grep -c $'\x1f''pane'$'\x1f''send-keys'$'\x1f''w1:p2'$'\x1f''enter' "$log") [ "$enter_count" -eq 1 ] || fail "send_text_submit must never retry past an unreadable target (that is a hard I/O failure, not a timing race), sent $enter_count Enter(s)" @@ -3531,10 +3546,12 @@ test_dispatch_composer_state_routes_by_backend() { } test_scripts_route_explicit_target_through_meta_backend() { - local dir state log resp fb neutral out + local dir state log resp fb neutral out lock_root dir="$TMP_ROOT/script-explicit-target"; state="$dir/state"; mkdir -p "$state" "$dir/responses" log="$dir/log"; resp="$dir/responses"; : > "$log" neutral="$dir/neutral-root"; mkdir -p "$neutral" + lock_root="$dir/locks" + write_adapter_owned_certificate "$lock_root" default fm_write_meta "$state/herdr-stale.meta" \ "window=default:w1:p2" "backend=herdr" \ "herdr_workspace_id=w1" "herdr_tab_id=w1:t2" "herdr_pane_id=w1:p2" @@ -3554,6 +3571,7 @@ SH out=$( PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$neutral" FM_STATE_OVERRIDE="$state" \ FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ + FM_BACKEND_HERDR_SERVER_LOCK_ROOT="$lock_root" \ "$ROOT/bin/fm-peek.sh" default:w1:p2 5 2>/dev/null ) [ "$out" = "captured herdr pane" ] || fail "fm-peek did not capture through herdr for an explicit metadata-matched target, got '$out'" assert_contains "$(cat "$log")" $'\x1f''pane'$'\x1f''read'$'\x1f''w1:p2' \ @@ -3562,6 +3580,7 @@ SH : > "$log" PATH="$fb:$PATH" FM_ROOT_OVERRIDE="$neutral" FM_HOME="$neutral" FM_STATE_OVERRIDE="$state" \ FM_HERDR_LOG="$log" FM_HERDR_RESPONSES="$resp" \ + FM_BACKEND_HERDR_SERVER_LOCK_ROOT="$lock_root" \ "$ROOT/bin/fm-send.sh" default:w1:p2 --key Escape >/dev/null 2>&1 expect_code 0 $? "fm-send --key should route an explicit metadata-matched target through herdr" assert_contains "$(cat "$log")" $'\x1f''pane'$'\x1f''send-keys'$'\x1f''w1:p2'$'\x1f''escape' \