Skip to content

fix(bin): allow guarded teardown of endpoint-less tmux husk records - #7

Merged
jokim1 merged 5 commits into
lila-mainfrom
fm/fm-teardown-endpointless-husk-path
Aug 18, 2026
Merged

fix(bin): allow guarded teardown of endpoint-less tmux husk records#7
jokim1 merged 5 commits into
lila-mainfrom
fm/fm-teardown-endpointless-husk-path

Conversation

@jokim1

@jokim1 jokim1 commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Intent

Close the teardown tool gap for endpoint-less terminal task records ("husks") without weakening unlanded-work protection.

Defect: bin/fm-teardown.sh refuses endpoint-less TERMINAL task records even with --force, because the missing/ambiguous-window endpoint check runs BEFORE force's check-skipping. Result: a husk record whose work is fully landed/remote-preserved has NO guarded cleanup path at all.

Required shape: Add a guarded husk path: teardown of an endpoint-less record is allowed ONLY when the landed-work test passes. The unlanded-work protection must not weaken in any other case. fm-teardown.sh owns the complete landed-work test; preserve the positive-landing-proof requirement from commit 6c6a22f (never bypass it for husks). --force semantics for records WITH endpoints must be unchanged. MINIMAL fix: bias to subtraction and the smallest guarded opening.

v7 approach (panel-passed under captain ruling A): name-independent proof that no live tmux server remains for this uid — protocol probe of connectable uid unix sockets (tmux -S display-message #{pid}), plus add-only pgrep -x tmux secondary that may only enlarge the refuse set. Process name never authorizes absence. Husk only for exact ship/scout tmux meta shape (zero window=, exact endpoint_task_id, worktree, project, backend absent-or-tmux). Husk teardowns still require land/scout gates under --force. Refuse husk absence proof when no hard-bounded probe runner (gtimeout/timeout/perl) exists. Colocated regression tests under tests/fm-teardown-husk.test.sh.

Follow-up required for green CI: portable-serial shard 3 timed out twice at the 15-minute hang tripwire after the new husk script rebalanced that shard; raise the portable-serial hang tripwire to 20 minutes so live 8-14 minute shard walls keep margin (hang tripwire, not expected healthy end).

Hard boundaries: PR target = origin fork (jokim1/firstmate) ONLY; never upstream (kunchenguid/firstmate). Never run real teardown against live home records.

What Changed

  • bin/fm-teardown.sh now permits teardown of endpoint-less TERMINAL task records ("husks") that previously had no cleanup path: the missing/ambiguous-endpoint refusal is skipped only for records matching the exact ship/scout tmux meta shape (zero window=, exact endpoint_task_id, worktree, project, tmux-or-absent backend), and only after a name-independent proof that no live tmux server remains for the uid — a protocol probe of connectable uid unix sockets plus an add-only pgrep -x tmux pass that can only enlarge the refuse set. Land/scout gates still run under --force, the positive-landing-proof requirement is preserved, and the absence proof is refused when no hard-bounded probe runner (gtimeout/timeout/perl) is available.
  • Added tests/fm-teardown-husk.test.sh covering the husk teardown path, including live real-tmux server refusal and renamed-binary protocol proof.
  • Raised the CI portable-serial shard hang tripwire from 15 to 20 minutes in .github/workflows/ci.yml (the new husk script rebalanced shard 3), and noted the endpoint-less husk teardown exception in docs/configuration.md.

Risk Assessment

✅ Low: The change adds a tightly-shaped, conservative husk teardown path guarded by a name-independent absence proof and the preserved positive-landing gate, with all non-husk semantics provably identical and colocated regression tests; it fully satisfies the authoritative intent.

Testing

Ran the new colocated husk regression suite (7/7, exercising real tmux servers for the live-server and renamed-binary absence proofs) plus the two sibling teardown suites (endpoint-safety 7/7, full teardown 73/73) with zero failures, confirming the guarded husk opening and that unlanded-work protection and endpoint-bearing --force semantics are unchanged. I also drove the real fm-teardown.sh end-to-end as an operator and captured a CLI transcript showing a landed husk torn down, an unlanded husk refused under --force with commits preserved, and a landed husk refused while a live tmux server answers. This is a shell CLI change with no rendered UI surface, so the operator CLI transcript is the appropriate product-level artifact rather than a screenshot.

Evidence: fm-teardown husk end-to-end CLI transcript (allow landed / refuse unlanded --force / refuse live-server)

SCENARIO 1 — landed husk: teardown husk-landed complete ... exit=0, meta REMOVED SCENARIO 2 — husk --force with unlanded commit: REFUSED: worktree ... has work not on any remote and not landed. unpushed commits: 2a0d140 unlanded ... exit=1, meta STILL PRESENT SCENARIO 3 — landed husk with live tmux server --force: REFUSED: husk husk-live: live tmux server(s) present; endpoint absence not positively proven. exit=1, meta STILL PRESENT


══════════════════════════════════════════════════════════════
SCENARIO 1 — landed husk (endpoint-less, work on origin/main): GAP CLOSED, teardown ALLOWED
══════════════════════════════════════════════════════════════
$ fm-teardown.sh husk-landed        # no window= endpoint, HEAD == origin/main
/var/folders/vx/kq2w6_xj1sq82jh1gtsjl0_m0000gn/T//husk-demo.GCQw2u/landed/project: already current
teardown husk-landed complete (window , worktree /var/folders/vx/kq2w6_xj1sq82jh1gtsjl0_m0000gn/T//husk-demo.GCQw2u/landed/worktree)
Backlog: husk-landed just finished. Run tasks-axi done husk-landed --pr PR_URL, then run tasks-axi ready for dependency-cleared candidates, check date gates, and dispatch only work whose blockers are gone and date is due.
exit=0
meta: REMOVED (record torn down)

══════════════════════════════════════════════════════════════
SCENARIO 2 — husk with UNLANDED commit + --force: PROTECTION HELD, REFUSED
══════════════════════════════════════════════════════════════
$ fm-teardown.sh husk-unlanded --force   # commit not on origin
REFUSED: worktree /var/folders/vx/kq2w6_xj1sq82jh1gtsjl0_m0000gn/T//husk-demo.GCQw2u/unlanded/worktree has work not on any remote and not landed.
unpushed commits:
2a0d140 unlanded
Push the branch, land its PR, or get the captain's explicit OK to discard, then --force.
exit=1
meta: STILL PRESENT (unlanded work preserved)

══════════════════════════════════════════════════════════════
SCENARIO 3 — landed husk but a LIVE tmux server answers + --force: REFUSED (name-independent absence proof fails)
══════════════════════════════════════════════════════════════
$ fm-teardown.sh husk-live --force       # a live tmux server is running for this uid
REFUSED: husk husk-live: live tmux server(s) present; endpoint absence not positively proven.
exit=1
meta: STILL PRESENT (refused while server live)
Evidence: Evidence driver script (drives real bin/fm-teardown.sh)
#!/usr/bin/env bash
# End-to-end demonstration of the endpoint-less tmux husk teardown gap-close.
# Drives the REAL bin/fm-teardown.sh exactly as an operator would.
set -u
ROOT="$1"
WORK=$(mktemp -d "${TMPDIR:-/tmp}/husk-demo.XXXXXX")
TEARDOWN="$ROOT/bin/fm-teardown.sh"
UID_N=$(id -u)

make_case() {  # <name> [extra-git]
  local name=$1 d="$WORK/$1" fb
  fb="$d/fakebin"
  mkdir -p "$d/home/state" "$d/home/data" "$d/home/config" "$d/worktree" "$d/project" "$fb"
  git init -q "$d/origin.git" --bare
  git -C "$d/origin.git" symbolic-ref HEAD refs/heads/main
  git clone -q "$d/origin.git" "$d/_seed" 2>/dev/null
  git -C "$d/_seed" -c user.email=t@t -c user.name=t commit -q --allow-empty -m base
  git -C "$d/_seed" push -q origin main; rm -rf "$d/_seed"
  git clone -q "$d/origin.git" "$d/project"
  git -C "$d/project" remote set-head origin main 2>/dev/null || true
  git -C "$d/project" worktree add -q -b fm/husk "$d/worktree" main
  printf '#!/usr/bin/env bash\nexit 1\n' > "$fb/pgrep"
  printf '#!/usr/bin/env bash\nfor a in "$@";do [ "$a" = -U ]&&exit 1;done\nexit 0\n' > "$fb/lsof"
  printf '#!/usr/bin/env bash\nexit 0\n' > "$fb/treehouse"
  printf '#!/usr/bin/env bash\ncase "${1:-} ${2:-}" in "pr list") printf "%%s\\n" "count: 0 (showing first 0)" "pull_requests[]: []";exit 0;; "pr view") echo "error: pull request not found">&2;exit 1;;esac\nexit 0\n' > "$fb/gh-axi"
  printf '#!/usr/bin/env bash\nexit 0\n' > "$fb/no-mistakes"
  chmod +x "$fb"/*
  touch "$d/home/state/.last-watcher-beat"
  printf '%s\n' "$d"
}
meta() {  # <dir> <id>
  local d=$1 id=$2
  printf 'endpoint_task_id=%s\nworktree=%s\nproject=%s\nkind=ship\nmode=direct-PR\nbackend=tmux\n' \
    "$id" "$d/worktree" "$d/project" > "$d/home/state/$id.meta"
}
run() { local d=$1 id=$2; shift 2
  env -u TMUX -u TMUX_PANE FM_GATE_REFUSE_BYPASS=1 FM_HOME="$d/home" FM_ROOT_OVERRIDE="$ROOT" \
    PATH="$d/fakebin:/opt/homebrew/bin:/usr/bin:/bin:$PATH" "$TEARDOWN" "$id" "$@"; }

sep() { printf '\n══════════════════════════════════════════════════════════════\n%s\n══════════════════════════════════════════════════════════════\n' "$1"; }

sep "SCENARIO 1 — landed husk (endpoint-less, work on origin/main): GAP CLOSED, teardown ALLOWED"
d=$(make_case landed); meta "$d" husk-landed
echo "\$ fm-teardown.sh husk-landed        # no window= endpoint, HEAD == origin/main"
run "$d" husk-landed; rc=$?
echo "exit=$rc"
[ -f "$d/home/state/husk-landed.meta" ] && echo "meta: STILL PRESENT" || echo "meta: REMOVED (record torn down)"

sep "SCENARIO 2 — husk with UNLANDED commit + --force: PROTECTION HELD, REFUSED"
d=$(make_case unlanded)
printf 'unlanded-only-here\n' > "$d/worktree/only-here"
git -C "$d/worktree" add only-here
git -C "$d/worktree" -c user.email=t@t -c user.name=t commit -q -m unlanded
meta "$d" husk-unlanded
echo "\$ fm-teardown.sh husk-unlanded --force   # commit not on origin"
run "$d" husk-unlanded --force; rc=$?
echo "exit=$rc"
[ -f "$d/home/state/husk-unlanded.meta" ] && echo "meta: STILL PRESENT (unlanded work preserved)" || echo "meta: REMOVED"

sep "SCENARIO 3 — landed husk but a LIVE tmux server answers + --force: REFUSED (name-independent absence proof fails)"
REAL_TMUX=$(command -v tmux || true)
if [ -n "$REAL_TMUX" ]; then
  d=$(make_case live); meta "$d" husk-live
  sock="$d/live.sock"
  env -u TMUX -u TMUX_PANE "$REAL_TMUX" -S "$sock" new-session -d -s husklive -n "fm-husk-live"
  echo "\$ fm-teardown.sh husk-live --force       # a live tmux server is running for this uid"
  env -u TMUX -u TMUX_PANE FM_GATE_REFUSE_BYPASS=1 FM_HOME="$d/home" FM_ROOT_OVERRIDE="$ROOT" \
    FM_GATE_REFUSE_BYPASS=1 PATH="/opt/homebrew/bin:/usr/bin:/bin:$PATH" "$TEARDOWN" husk-live --force; rc=$?
  env -u TMUX -u TMUX_PANE "$REAL_TMUX" -S "$sock" kill-server 2>/dev/null || true
  echo "exit=$rc"
  [ -f "$d/home/state/husk-live.meta" ] && echo "meta: STILL PRESENT (refused while server live)" || echo "meta: REMOVED"
else
  echo "tmux not installed; skipped"
fi

rm -rf "$WORK"

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-teardown-husk.test.sh — 7/7 pass, including live real-tmux server refusal and X6 renamed-binary protocol proof
  • bash tests/fm-teardown-endpoint-safety.test.sh — 7/7 pass (endpoint validation unchanged)
  • bash tests/fm-teardown.test.sh — 73/73 pass (no regression in dirty/land/lock-recovery gates)
  • End-to-end: ran real bin/fm-teardown.sh against husk fixtures via evidence driver — landed husk ALLOWED (exit 0, record removed); unlanded husk --force REFUSED (unpushed commits preserved, meta kept); landed husk with live tmux server --force REFUSED via absence proof
  • Reviewed CI hang-tripwire raise 15→20 min in .github/workflows/ci.yml and the husk-exception note in docs/configuration.md
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

jokim1 added 4 commits August 17, 2026 14:12
…nt absence proof

Terminal ship/scout records with no window= target could not be cleaned up even
when work was fully landed, because endpoint validation ran before force and
land checks. Open a guarded husk path only for exact tmux husk shape, only after
protocol-level proof that no live tmux server remains for this uid (plus add-only
name secondary), and never skip land/scout gates under --force for husks.
@cursor

cursor Bot commented Aug 17, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Live serial shard walls are commonly 8-14 minutes; the 15-minute cap left
almost no margin once a new husk regression script rebalanced shard 3 over
the limit. Keep the job as a hang tripwire, not the expected healthy end.
@jokim1
jokim1 merged commit cf49ebc into lila-main Aug 18, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant