Merge pull request #602: fix(remote): load a remote session's scrollback from the remote tmux

A full-history load of a remote SSH session reads the scrollback from the
remote tmux in one bounded ssh round trip and splices it above the local
frame. A transport failure (ssh 255, timeout, unparseable output) starts a
30 s per-host back-off; a missing remote session is per-session only.

Conflict resolved in session-routes.ts: #602's remote scrollback read runs
before #601's capture helper and its alternate-screen fallback.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-10-13 18:01:15 +02:00
commit f9f00bdb5e
11 files changed
+720 -11

No files matched your search

+1 -1
View File
@@ -77,7 +77,7 @@ OMP's registry entry declares `privilegedParams: []` and a launch spec that only
Further detail: ⚠️ **Auto-reconnect revives ONLY when the durable remote tmux session is verifiably still alive** (`remoteTmuxSessionAlive()`, a `has-session` probe over ssh, #355): a clean agent exit (Ctrl-C, Ctrl-D, `exit`) tears that session down, and `isPaneDead()` cannot tell it from a transport drop, so the watcher used to relaunch a FRESH agent after every clean exit (claude only looked fine because its `|| --resume` fallback masked it). An unreachable host answers `undefined`, which also means do not revive. ⚠️ `has-session` prints NOTHING on success, so the probe is classified by EXIT STATUS (`classifyRemoteAliveExit`: 0 alive, ssh's 255 or a timeout unknown, anything else gone); reading stdout classified every live session as gone and silently disabled transport-drop reconnects. The answer is cached per session and forgotten whenever the pane is seen alive again, or a stale `true` from one transport drop would revive the next clean exit.
File reads in a remote case are the second ssh surface (see Remote SSH cases below). The reason writes, office previews and thumbnails are deliberately unsupported over ssh is that no remote file is ever copied onto the server's disk. ssh children are bounded because terminal output in a remote session is written on the remote host, so a prompt-injected agent there can print hundreds of `codeman://attach` links.
File reads in a remote case are the second ssh surface (see Remote SSH cases below). The reason writes, office previews and thumbnails are deliberately unsupported over ssh is that no remote file is ever copied onto the server's disk. ssh children are bounded because terminal output in a remote session is written on the remote host, so a prompt-injected agent there can print hundreds of `codeman://attach` links. The third surface is a remote session's full-history load, which reads the scrollback from the remote tmux (`src/remote-pane-history.ts`) through the same limiter, with a bounded slot wait (`maxWaitMs`) since the local capture is its fallback.
### Remote SSH cases
+28
View File
@@ -251,6 +251,34 @@ unreachable host answers "unknown", which also means do not revive. The answer
is cached per session and cleared whenever the pane is next seen alive, so a
stale `true` from one transport drop can never revive the NEXT clean exit.
## Terminal history from the remote tmux
The local pane runs the ssh attach, so it only sees the remote tmux client's
full-screen repaints and keeps a fraction of the scrollback (none after a
reattach). A full-history load (`GET /api/sessions/:id/terminal?full=1`) of a
remote session therefore reads the scrollback rows from the REMOTE pane in one
ssh round trip (`src/remote-pane-history.ts`) and splices them above the local
visible frame and caret. Bounded (5 s for the round trip, at most 1.5 s queued
for a slot in the shared ssh limiter) and fail-soft: any failure keeps the
local capture, and a transport failure (ssh exit 255, a timeout, output that is
not the script's) also skips that host for 30 s; a failure of the remote
command itself (a remote tmux session that is gone) affects that session only.
How much history exists is the remote tmux's own `history-limit`, which the
launch command does not set (tmux's default is 2000 lines).
Two seams can drop rows at the join, silently. Neither loses anything that
loaded before this existed:
- **Rows that scroll during the round trip.** The remote history is read
first and the local frame after it, so rows that scrolled off the remote
screen in between are in neither (measured: 13 lines lost with a 150 ms
round trip and fast output).
- **A shared remote session with a taller client.** Under `window-size latest`
a remote session attached elsewhere is sized by its latest client; when that
client is taller than this pane, the rows between this pane's frame and the
remote screen's top are in neither the remote history nor the local frame
(measured: 20 rows).
## File access over SSH
A remote case's `workingDir` is an absolute path on the **remote** host