Skip to content

Attach the terminal to its tmux session again when its exec ends - #114

Merged
splitbrain merged 1 commit into
splitbrain:mainfrom
splitbrain-bot:terminal-reattach
Oct 11, 2026
Merged

splitbrain merged 1 commit into
splitbrain:mainfrom
splitbrain-bot:terminal-reattach

Conversation

@splitbrain-bot

Copy link
Copy Markdown

Part of #109 (step 3).

Changes

  • When the exec that carries the pty ends while the WebSocket is open, the orchestrator waits one second and opens a
    new exec that attaches to the same tmux client session by its name (openTerminalExec with the reattach
    argument in docker.ts). The WebSocket stays open, and tmux keeps the screen. Before, the socket closed with
    "shell ended" on every ended exec, and the user had to press Reconnect.
  • Text typed and resizes sent in the gap are kept and applied after the attach, as for a box that is still starting.
  • A reattach never starts a stopped box. When the container is not running, the socket closes with "box stopped".
  • When tmux reports no such session, the reattach shell exits with code 3 (NO_TERMINAL_SESSION). The user ended
    the shell, and the socket closes with "shell ended" as before. This now comes about one second later than before.
  • A reattach that fails for another reason is tried once more after five seconds, and then the socket closes with
    the error.
  • doc/terminal.md describes the reattach.

Change in behavior: a tmux detach (prefix, then d) now reattaches after one second. Before, it closed the
terminal. The client session still exists after a detach, so the orchestrator cannot tell it from a cut stream.

Tests

  • Orchestrator: tsc --noEmit is clean, 886 tests passed, 1 skipped (as before). terminal.test.ts has six new
    tests: reattach to the same session with text typed in the gap, "shell ended" on exit code 3, "box stopped"
    without a box start, one failed try followed by one that works, two failed tries, and a browser that closes in
    the gap. The test fake now opens a new pty for each exec and reports a container state.
  • The reattach shell was checked against tmux 3.6: the client session stays when its exec is cut, the attach
    connects to it, and a missing session gives exit code 3.

Not tested against a real Docker daemon.

Notes for the next steps

  • Step 7, the Runtime interface. The reattach is a second spawn of the terminal with a different command,
    and a containerState read between them. Both go through the interface without a change. The names
    REATTACH_DELAY_MS and REATTACH_RETRY_MS in terminal.ts are the one second and the five seconds of section
    12 of the concept.
  • Step 8, conformance tests. A conformance test can cut the stream of a terminal exec and check that an attach
    by session name shows the same screen, and that the exit code of the attach is 3 when the session is gone.
  • Step 9, Kubernetes. The reattach reads the container state, not the pod phase. With the runtime interface,
    this is the state of the box, where a pod in Pending counts as running. In that case the attach fails,
    and the second try after five seconds applies.

When the exec that carried the pty ended, the orchestrator closed the
WebSocket, and the user had to press Reconnect. On a runtime where a
long-lived stream can end at any time, for example through an idle
timeout of the container runtime, this broke an open terminal without a
reason that the user could see.

The orchestrator now waits one second after the exec ended and opens a
new exec that attaches to the same tmux client session by its name. The
WebSocket stays open, and tmux keeps the screen. Text typed in the gap
is kept and sent after the attach. A reattach never starts a stopped
box: when the box has stopped, the socket closes with "box stopped".
When tmux reports no such session, the user ended the shell, and the
socket closes with "shell ended" as before. A reattach that fails for
another reason is tried once more after five seconds, and then the
socket closes with the error.

Part of splitbrain#109
@splitbrain
splitbrain merged commit 2eda28a into splitbrain:main Oct 11, 2026
3 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.

2 participants