From ab5f92055c8ffc2be7fe5cd7fd3eb527efe5feeb Mon Sep 17 00:00:00 2001 From: Marcus Nascimento Date: Sun, 2 Aug 2026 07:25:42 -0400 Subject: [PATCH 1/2] fix(bin): accept legacy non-tmux meta files missing endpoint_task_id= in teardown validation fm_backend_validate_task_endpoint required an exact endpoint_task_id= match for herdr, zellij, orca, and cmux backends, refusing teardown forever for older tasks that predate that field. The tmux backend never had this check, relying instead on window naming convention. Change the per-backend binding check from a strict match to only refuse when endpoint_task_id= is present AND mismatches the task id. Legacy meta files with no endpoint_task_id= field now pass through, while genuine cross-task binding mismatches are still caught by the existing top-level guard. --- bin/fm-backend.sh | 24 ++++++++--------- tests/fm-teardown-endpoint-safety.test.sh | 32 ++++++++++++++++++++++- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/bin/fm-backend.sh b/bin/fm-backend.sh index e505b99f75..50be95f499 100644 --- a/bin/fm-backend.sh +++ b/bin/fm-backend.sh @@ -452,10 +452,10 @@ fm_backend_validate_task_endpoint() { # fi ;; herdr) - [ "$binding" = "$id" ] || { - echo "REFUSED: legacy Herdr endpoint metadata for task $id lacks an exact task binding; preserving task state." >&2 + if [ -n "$binding" ] && [ "$binding" != "$id" ]; then + echo "REFUSED: Herdr endpoint metadata is bound to task $binding, not $id; preserving task state." >&2 return 1 - } + fi recorded_session=$(fm_backend_meta_exact_value "$meta" herdr_session) || recorded_session= workspace=$(fm_backend_meta_exact_value "$meta" herdr_workspace_id) || workspace= tab=$(fm_backend_meta_exact_value "$meta" herdr_tab_id) || tab= @@ -471,10 +471,10 @@ fm_backend_validate_task_endpoint() { # fi ;; zellij) - [ "$binding" = "$id" ] || { - echo "REFUSED: legacy Zellij endpoint metadata for task $id lacks an exact task binding; preserving task state." >&2 + if [ -n "$binding" ] && [ "$binding" != "$id" ]; then + echo "REFUSED: Zellij endpoint metadata is bound to task $binding, not $id; preserving task state." >&2 return 1 - } + fi recorded_session=$(fm_backend_meta_exact_value "$meta" zellij_session) || recorded_session= tab=$(fm_backend_meta_exact_value "$meta" zellij_tab_id) || tab= pane=$(fm_backend_meta_exact_value "$meta" zellij_pane_id) || pane= @@ -487,10 +487,10 @@ fm_backend_validate_task_endpoint() { # fi ;; orca) - [ "$binding" = "$id" ] || { - echo "REFUSED: legacy Orca endpoint metadata for task $id lacks an exact task binding; preserving task state." >&2 + if [ -n "$binding" ] && [ "$binding" != "$id" ]; then + echo "REFUSED: Orca endpoint metadata is bound to task $binding, not $id; preserving task state." >&2 return 1 - } + fi terminal=$(fm_backend_meta_exact_value "$meta" terminal) || terminal= worktree_id=$(fm_backend_meta_exact_value "$meta" orca_worktree_id) || worktree_id= [ -n "$terminal" ] || { @@ -510,10 +510,10 @@ fm_backend_validate_task_endpoint() { # window=$terminal ;; cmux) - [ "$binding" = "$id" ] || { - echo "REFUSED: legacy cmux endpoint metadata for task $id lacks an exact task binding; preserving task state." >&2 + if [ -n "$binding" ] && [ "$binding" != "$id" ]; then + echo "REFUSED: cmux endpoint metadata is bound to task $binding, not $id; preserving task state." >&2 return 1 - } + fi workspace=$(fm_backend_meta_exact_value "$meta" cmux_workspace_id) || workspace= surface=$(fm_backend_meta_exact_value "$meta" cmux_surface_id) || surface= if [ -z "$workspace" ] || [ -z "$surface" ] || [ "$window" != "$workspace:$surface" ] \ diff --git a/tests/fm-teardown-endpoint-safety.test.sh b/tests/fm-teardown-endpoint-safety.test.sh index 5786102cd3..4f417e260c 100755 --- a/tests/fm-teardown-endpoint-safety.test.sh +++ b/tests/fm-teardown-endpoint-safety.test.sh @@ -136,6 +136,36 @@ test_supported_backend_endpoint_records_validate() { "backend=cmux" "cmux_workspace_id=workspace-1" "cmux_surface_id=surface-2" fm_backend_validate_task_endpoint "$dir/home/state/$id.meta" "$id" || fail "valid cmux endpoint refused" + id=herdr-legacy + fm_write_meta "$dir/home/state/$id.meta" \ + "window=lab:w1:p2" "worktree=$dir/worktree" "project=$dir/project" \ + "backend=herdr" "herdr_session=lab" "herdr_workspace_id=w1" "herdr_tab_id=w1:t2" "herdr_pane_id=w1:p2" + fm_backend_validate_task_endpoint "$dir/home/state/$id.meta" "$id" || fail "legacy Herdr endpoint (no binding) refused" + [ "$FM_BACKEND_VALIDATED_BACKEND" = herdr ] || fail "legacy Herdr did not select its backend" + [ "$FM_BACKEND_VALIDATED_TARGET" = "lab:w1:p2" ] || fail "legacy Herdr target mismatch" + + id=zellij-legacy + fm_write_meta "$dir/home/state/$id.meta" \ + "window=lab:7" "worktree=$dir/worktree" "project=$dir/project" \ + "backend=zellij" "zellij_session=lab" "zellij_tab_id=3" "zellij_pane_id=7" + fm_backend_validate_task_endpoint "$dir/home/state/$id.meta" "$id" || fail "legacy Zellij endpoint (no binding) refused" + [ "$FM_BACKEND_VALIDATED_BACKEND" = zellij ] || fail "legacy Zellij did not select its backend" + + id=orca-legacy + fm_write_meta "$dir/home/state/$id.meta" \ + "window=fm-$id" "terminal=term-7" \ + "worktree=$dir/worktree" "project=$dir/project" "backend=orca" "orca_worktree_id=worktree-9" + fm_backend_validate_task_endpoint "$dir/home/state/$id.meta" "$id" || fail "legacy Orca endpoint (no binding) refused" + [ "$FM_BACKEND_VALIDATED_BACKEND" = orca ] || fail "legacy Orca did not select its backend" + [ "$FM_BACKEND_VALIDATED_TARGET" = term-7 ] || fail "legacy Orca did not select its terminal" + + id=cmux-legacy + fm_write_meta "$dir/home/state/$id.meta" \ + "window=workspace-1:surface-2" "worktree=$dir/worktree" "project=$dir/project" \ + "backend=cmux" "cmux_workspace_id=workspace-1" "cmux_surface_id=surface-2" + fm_backend_validate_task_endpoint "$dir/home/state/$id.meta" "$id" || fail "legacy cmux endpoint (no binding) refused" + [ "$FM_BACKEND_VALIDATED_BACKEND" = cmux ] || fail "legacy cmux did not select its backend" + for backend in tmux herdr zellij orca cmux; do set +e fm_backend_kill "$backend" "" >/dev/null 2>&1 @@ -143,7 +173,7 @@ test_supported_backend_endpoint_records_validate() { set -e [ "$target" -ne 0 ] || fail "$backend generic kill accepted an empty target" done - pass "cleanup identity: valid tmux, Herdr, Zellij, Orca, and cmux records validate while every empty backend target refuses" + pass "cleanup identity: valid and legacy tmux, Herdr, Zellij, Orca, and cmux records validate while every empty backend target refuses" } test_tmux_empty_target_refuses_without_invocation() { From 9ac48ec940dd882a2996a041addc52e1ec627912 Mon Sep 17 00:00:00 2001 From: Marcus Nascimento Date: Sun, 2 Aug 2026 07:32:14 -0400 Subject: [PATCH 2/2] no-mistakes(document): docs(configuration): fix stale legacy-binding requirement claim --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 7b90f8ec15..0133d2a40f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -80,7 +80,7 @@ Only metadata-routed task selectors carry secondmate-marker and Codex-harness co These five sentences are the single owner of the task-selector vocabulary; backend guides and other documents point here instead of restating the resolution order. `fm-teardown.sh ` takes a task id directly and validates the complete metadata-only endpoint identity before any runtime dispatch or cleanup mutation. Missing, empty, duplicate, malformed, backend-inconsistent, or task-mismatched endpoint records are preserved and refused. -Legacy tmux metadata remains cleanup-compatible when its exact window name is `fm-`; opaque non-tmux endpoints require their recorded `endpoint_task_id=` binding. +Legacy tmux metadata remains cleanup-compatible when its exact window name is `fm-`; opaque non-tmux endpoints refuse only when a recorded `endpoint_task_id=` binding is present and mismatches the task id, so legacy metadata predating that field also stays cleanup-compatible. `FM_HOME` determines Herdr's home label: the primary home uses `firstmate`, and a secondmate home marked by `.fm-secondmate-home` uses `2ndmate-`. [`herdr-backend.md`](herdr-backend.md#watching-and-task-containers) owns launcher-bound workspace placement, the label-only fallback, collision handling, and recovery behavior. The optional local `config/herdr-presentation-spaces` presence flag instead enables Herdr's default-off disposable single-task visual projection; [Optional presentation spaces](herdr-backend.md#optional-presentation-spaces) owns its behavior, safety limits, recovery contract, and narrow locked session-start cleanup of exact restored idle-shell children.