Skip to content

relay#543: host sessions swap in a tmux pane - #571

Merged
barelyworkingcode merged 7 commits into
epic/525-session-adoptionfrom
feat/543-tmux-pane-swap
Oct 21, 2026
Merged

barelyworkingcode merged 7 commits into
epic/525-session-adoptionfrom
feat/543-tmux-pane-swap

Conversation

@barelyworkingcode

Copy link
Copy Markdown
Owner

Closes #543

Host Claude sessions on a host with tmux 3.0 or later run their runner as the pane of tmux session relay-agent-<session id>. A drop-in respawns that pane with Claude's UI on the same conversation; closing the terminal, or quitting Claude, respawns the runner in the same pane. A host without tmux 3.0 runs the runner alone, as before. Delete ends the tmux session.

Part of the epic #525. Contract and Amendments 1 and 2 are on the issue.

Non-obvious decisions

  • The pane's shell changes into the project directory itself. tmux expands a respawn-pane -c value as a format, so a directory holding #P would start the UI elsewhere (Amendment 2, reproduced on the verify stack).
  • The runner's awk version probe reads /dev/null. macOS awk reads -W version as a program and blocked on the pane's pty (Amendment 1).
  • The stub host's tmux cleanup names its socket with -S. The TMUX_TMPDIR form fell back to /tmp once the test dir was gone and killed the machine's own tmux server.
  • Nothing a user sees on screen changes; the power path is tmux attach -t relay-agent-<id> on the host and hosts[].tmux_path.

Notes

  • Minor (review round 2): on a passing test, the sshstub -S cleanup finds no socket, because the instance cleanup removes the directory first; killUnder ends the stub server then. The comment claims a bit more than the code does.
  • Minor (review round 1): TestHostWithoutTmuxDropInUsesTerminal guards the criterion (runner alone, plain drop-in) but cannot go red if only the version gate is deleted, since the fake tmux 2.9 fails every other call too.
  • session-swap-back now answers BLOCKED on a loopback host with no tmux, instead of running its plain swap. It adds pane checks and removes none.
  • Theoretical, not reproduced: the Ctrl-D write in TestHostPaneQuitClaudeHandsBack can race the client's raw-mode switch; a start timeout in pane mode does not kill a late runner; a swap back acts on the active pane if the owner splits the window.
  • Test host: the test machine itself over ssh, tmux 3.7c.

Cost

  • Fix attempts: 3 on top of the build (awk probe stdin, # directory, harness cleanup), each one round; plus one journey fix (tmux_path not written).
  • Review rounds: 2, both default model.
  • Tokens: about 1.1M across subagents.

Evidence

  • e2e suite at 1519ad2: ok relaye2e/features 232.163s (baseline at 49e2279: ok, 461 tests).
  • devboxverify --stack --phase api on a stack built at 1519ad2: pass=8 fail=0 blocked=0 notrun=2; session-swap-back PASS (host tmux 3.7c; pane pid = runner.json pid before, claude --resume during, new runner after, one session_created), session-detached-host PASS. Run while the full suite ran alongside.
  • Real-app check on the verify stack: a loopback host project; relay drop-in in a desktop Terminal showed Claude's UI in relay-agent-<id>; closing the window handed back (pane pid = new runner pid, same session_created); the next message was answered; delete ended the tmux session. A project dir with C#Projects started the UI in the home dir before the fix and in the project dir after.

Review: no blocking findings (round 2, default model; 1 minor in notes)
Real-app check: pass on the verify stack at 1519ad2, tmux 3.7c; the '#'-dir drop-in seen in the home dir before, in the project dir after
Journey: session-swap-back extended with the pane leg; PASS on stack r543 at 1519ad2
Harness weakening: none; session-swap-back only adds checks (BLOCKED, not PASS, on a host without tmux)
Main harness vs head: epic/525 harness against a stack built at 1519ad2: pass=8 fail=0 blocked=0 notrun=2
Break-and-restore: swap back made to create a new tmux session; TestHostPaneSwapToTerminalAndBack red on session_created; restored byte for byte (sha1 matched)

🤖 Generated with Claude Code

barelyworkingcode and others added 7 commits October 20, 2026 23:14
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…(red)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@barelyworkingcode
barelyworkingcode merged commit d26ab6b into epic/525-session-adoption Oct 21, 2026
@barelyworkingcode

Copy link
Copy Markdown
Owner Author

merge-check: merged. approved, target, contract, evidence, owner, weakening

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