Skip to content

fix(tmux): disambiguate numeric session names when creating task windows#406

Open
mikaellinares87-code wants to merge 2 commits into
kunchenguid:mainfrom
mikaellinares87-code:fm/fm-spawn-numeric-session
Open

fix(tmux): disambiguate numeric session names when creating task windows#406
mikaellinares87-code wants to merge 2 commits into
kunchenguid:mainfrom
mikaellinares87-code:fm/fm-spawn-numeric-session

Conversation

@mikaellinares87-code

Copy link
Copy Markdown

Intent

Fix a live-hit bug in firstmate's own tmux backend adapter (bin/backends/tmux.sh): fm_backend_tmux_create_task passed a bare session name to tmux's -t target in both its duplicate-check (list-windows) and its new-window create call. When the session name is purely numeric (e.g. "0", tmux's own session-name default), tmux's target-window resolution for new-window prefers a window-INDEX match in the current session over a session-NAME match, and index 0 is always occupied by that session's initial window - so a spawn against a numeric-named session failed with the exact live error "create window failed: index 0 in use" even though the session name itself was free. This blocked a real secondmate launch on 2026-07-09 and forced a manual session-rename workaround. Empirically confirmed live (real tmux 3.4, private socket) that appending a trailing colon ("$ses:") forces session-name interpretation and fixes it; a leading "=$ses" was also tried and still collided, so the colon form is the deliberate, verified fix - not a guess. Scope, per the task brief: (1) apply the trailing-colon fix at fm_backend_tmux_create_task's tmux calls, (2) audit the rest of bin/backends/tmux.sh and other backend files for the same bare-session-name ambiguity (audited; no other call site passes a bare session name to -t), (3) add a regression test extending the existing real-tmux smoke suite (tests/fm-backend-tmux-smoke.test.sh) reproducing the exact bug with a session literally named "0" whose window index 0 is occupied, verified empirically to fail on the pre-fix code and pass on the fix, (4) shellcheck-clean. This is firstmate's own shared template repo, so the firstmate-coding-guidelines skill's repo style rules apply. The change is deliberately minimal and surgical: 2 tmux -t targets changed in bin/backends/tmux.sh plus one new self-contained test block, plus doc updates (CONTRIBUTING.md, docs/tmux-backend.md) - no refactor of unrelated code. Captain-authorized scope note: an earlier run of this same intent had its test step auto-fix an UNRELATED pre-existing flaky test in bin/fm-wake-lib.sh (PID-identity derivation broken by WSL2 clock drift) - that fix duplicates already-open upstream PR #392 by a different author and is explicitly OUT OF SCOPE for this PR; the captain decided to drop it and keep this PR minimal, and this branch has been corrected to exclude it (only the tmux fix commit plus its docs commit remain). If the test step hits that same unrelated flake again, do not re-patch fm-wake-lib.sh into this branch - treat it as a known pre-existing environment flake with an already-open upstream fix (PR #392) and respond to the gate accordingly to keep this PR scoped to the tmux fix only. Push target: the gate is now configured with --fork-url so it pushes fm/fm-spawn-numeric-session to the fork remote (mikaellinares87-code/firstmate, captain-authorized, shared with a sibling task) and opens the PR cross-repo against kunchenguid/firstmate automatically.

What Changed

  • fm_backend_tmux_create_task in bin/backends/tmux.sh now appends a trailing colon to its -t targets ("$ses:") in both the duplicate-check list-windows call and the new-window call, forcing tmux to resolve the target as a session name. Previously a purely numeric session name (e.g. "0", tmux's own default) was resolved as a window index — which is always occupied by the session's initial window — so spawning a task failed with create window failed: index 0 in use even though the session name was valid. The trailing-colon form was verified live on tmux 3.4; a leading =$ses was also tried and still collided.
  • Adds a regression check to the real-tmux smoke suite (tests/fm-backend-tmux-smoke.test.sh) that reproduces the exact collision — a session literally named "0" whose window index 0 is occupied — confirmed to fail against the pre-fix code and pass with the fix.
  • Documents the root cause, the verified fix, and the audit result (no other call site in the tmux backend passes a bare session name to -t for task creation) in docs/tmux-backend.md, and updates the test-index line in CONTRIBUTING.md.

Risk Assessment

✅ Low: A two-line, empirically verified fix using the standard tmux session-disambiguation idiom, with a self-contained leak-safe regression test, accurate docs, no impact on the old-vs-new conformance byte-diff (both builds share the current backends dir), and a substantiated audit that no other call site is affected.

Testing

Ran the real-tmux smoke suite on the fixed code (all 8 checks pass, including the new numeric-session regression check), replayed the live bug end-to-end on real tmux 3.4 showing the pre-fix backend failing with the exact create window failed: index 0 in use error and the fixed backend succeeding, and confirmed the new regression test fails on pre-fix code and passes on the fix; the only failure encountered was a pre-existing, unrelated capture-bounds timing flake that passed on retry and in the baseline full-suite run.

Evidence: Before/after live replay: numeric session "0" spawn on real tmux 3.4 (pre-fix fails with 'index 0 in use', post-fix creates the window)

$ tmux -V tmux 3.4 === BEFORE: fm_backend_tmux_create_task from base 22b1d71 (bare -t "$ses") === live sessions: 0 windows in session 0 before: 0:bash create window failed: index 0 in use RESULT(pre-fix): fm_backend_tmux_create_task FAILED (exit 1) === AFTER: fm_backend_tmux_create_task from target 8384054 (-t "$ses:" trailing colon) === live sessions: 0 windows in session 0 before: 0:bash RESULT(post-fix): fm_backend_tmux_create_task SUCCEEDED windows in session 0 after: 0:bash 1:fm-demo-task

# Replaying the 2026-07-09 live failure: spawning a task window into a tmux
# session literally named "0" (tmux's own default session name), on a real
# tmux server over a private socket.

$ tmux -V
tmux 3.4

=== BEFORE: fm_backend_tmux_create_task from base 22b1d71 (bare -t "$ses") ===
live sessions: 0
windows in session 0 before: 0:bash 
create window failed: index 0 in use
RESULT(pre-fix): fm_backend_tmux_create_task FAILED (exit 1)

=== AFTER: fm_backend_tmux_create_task from target 8384054 (-t "$ses:" trailing colon) ===
live sessions: 0
windows in session 0 before: 0:bash 
RESULT(post-fix): fm_backend_tmux_create_task SUCCEEDED
windows in session 0 after:  0:bash 1:fm-demo-task 
Evidence: New regression test run against the pre-fix backend — fails at exactly the numeric-session check with the live error

ok - real tmux: fm_backend_tmux_kill removes the window and is idempotent/best-effort create window failed: index 0 in use not ok - fm_backend_tmux_create_task failed against a numeric session name (the -t 0 window-index collision regression) exit=1

# The NEW regression test (target 8384054) run against the PRE-FIX backend (base 22b1d71).
# (Temp-copy only: the pre-existing capture-bounds settle sleep was raised 0.6s -> 3s to
#  dodge an unrelated timing flake in this environment; the numeric-session block is unmodified.)
ok - real tmux: fm_backend_tmux_create_task creates a window and refuses a duplicate
ok - real tmux: fm_backend_tmux_send_text_line sends literal text and submits with Enter
ok - real tmux: fm_backend_tmux_send_literal + fm_backend_tmux_send_key Enter submit as two separate steps
ok - real tmux: fm_backend_tmux_capture's -S -N bound trims old history for a small window and reaches it for a large one
ok - real tmux: fm_backend_tmux_resolve_bare_selector (list-live) finds the created window by name
ok - real tmux: fm_backend_tmux_resolve_bare_selector fails for a window that does not exist
ok - real tmux: fm_backend_tmux_kill removes the window and is idempotent/best-effort
create window failed: index 0 in use
not ok - fm_backend_tmux_create_task failed against a numeric session name (the -t 0 window-index collision regression)
exit=1
Evidence: Full smoke suite on the fixed code — all 8 checks pass including the numeric-session regression check
ok - real tmux: fm_backend_tmux_create_task creates a window and refuses a duplicate
ok - real tmux: fm_backend_tmux_send_text_line sends literal text and submits with Enter
ok - real tmux: fm_backend_tmux_send_literal + fm_backend_tmux_send_key Enter submit as two separate steps
ok - real tmux: fm_backend_tmux_capture's -S -N bound trims old history for a small window and reaches it for a large one
ok - real tmux: fm_backend_tmux_resolve_bare_selector (list-live) finds the created window by name
ok - real tmux: fm_backend_tmux_resolve_bare_selector fails for a window that does not exist
ok - real tmux: fm_backend_tmux_kill removes the window and is idempotent/best-effort
ok - real tmux: fm_backend_tmux_create_task works against a numeric session name whose window index 0 is already in use
- Outcome: ⚠️ 1 info across 1 run (15m41s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ℹ️ docs/tmux-backend.md:139 - The added audit summary line states "No other call site in bin/backends/tmux.sh passes a bare session name to -t; every other target is already a full session:window pair", but fm_backend_tmux_container_ensure (bin/backends/tmux.sh:65) does pass a bare session name to -t (tmux has-session -t firstmate). It is a fixed non-numeric literal, so it can never hit the numeric window-index collision this doc describes, but the "every other target is already a full session:window pair" claim is slightly overbroad for a doc whose purpose is to record the audit result.
⚠️ **Test** - 1 info
  • ℹ️ tests/fm-backend-tmux-smoke.test.sh:102 - Pre-existing timing flake (unrelated to this change, present at base commit): the smoke test's capture-bounds section sleeps only 0.6s after sending an 80-line loop, and under load the pane shell hasn't finished, so a 3-line capture should still contain the most recent output intermittently fails before the suite reaches later checks. It passed in the baseline run and on retry. Per the author's explicit scope decision to keep this PR minimal (mirroring the fm-wake-lib.sh flake exclusion), no fix was applied to this branch.
  • command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"
  • Baseline (pre-ran successfully): for t in tests/*.test.sh; do bash "$t"; done against the target commit
  • bash tests/fm-backend-tmux-smoke.test.sh on the fixed code — all 8 checks pass, including the new numeric-session regression check (one retry needed due to a pre-existing unrelated capture-bounds timing flake)
  • Manual before/after replay on real tmux 3.4 over a private socket: sourced the base-commit (22b1d71) bin/backends/tmux.sh and called fm_backend_tmux_create_task "0" fm-demo-task against a session named "0" — fails with create window failed: index 0 in use; same call with the target-commit (8384054) backend succeeds and tmux list-windows shows 1:fm-demo-task created in session "0"
  • Ran the new regression test against the pre-fix backend in a temp repo layout — it fails at exactly the numeric-session check with the live error text, confirming the test catches the bug
  • Audit spot-check: grep -rn -- '-t "$ses' bin/ shows only the two fixed -t "$ses:" sites, no remaining bare session-name targets
  • Cleanup verification: git status --porcelain clean, temp demo dirs removed, no stray private-socket tmux servers
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Mikael Linares added 2 commits July 10, 2026 01:17
…te_task

A tmux session literally named "0" (tmux's own session-name default) made
bare `-t "$ses"` targets ambiguous: tmux prefers a window-index match in the
current session over a session-name match, and index 0 is always occupied
by the session's initial window, so new-window failed with "index 0 in use"
even though the session name was free. Force session-name interpretation
with a trailing colon at both -t call sites. Reproduced live 2026-07-09.
@kunchenguid

kunchenguid commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Thanks for the PR! This branch currently has a merge conflict with the base branch.

When you get a chance, please rebase onto (or merge) the latest base branch, resolve the conflict, and push. After that, checks will re-run and the PR will get looked at again.

Noted for firstmate#406 at 8384054a.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants