You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Full-history tmux snapshot leaves status-bar SGR active, coloring subsequent text and erased cells #629
Codeman 1.41.1 intermittently paints input text and blank parts of terminal rows green when attached to an existing tmux session with a green status bar. Resizing the browser clears it. Enabling/disabling WebGL makes no difference.
The outer Codeman pane's capture-pane -p -e output ends with the tmux status bar and \x1b[30m\x1b[42m, without a reset.
Reproduced cause
capturePaneBuffer(..., { fullHistory: true }) in src/tmux-manager.ts normalizes the capture and appends formatCursorRestore(geometry). Cursor movement leaves the final captured row's SGR active when live updates resume. Its missing-geometry fallback also lacks a trailing reset. The ordinary visible-frame snapshot path already resets styles; this report concerns full-history restores.
Deterministic browser regression
Exercise the installed package's real TmuxManager.prototype.capturePaneBuffer with a fake tmux returning:
Render the returned snapshot in the package's xterm.js in headless Chromium (cols: 40, rows: 2). Then append an incremental update without a new SGR:
terminal.write('\x1b[1;1Htyped\x1b[K');
Before the correction, both the text and erased cells inherit background index 2 (green). The status bar should remain green, while this update should use the default background. Test again with the fake cursor query failing to exercise the fallback.
Tested local correction
Reset SGR after the full-history capture, before restoring its cursor:
The browser regression passes for both branches after this correction: the status remains green, subsequent text uses the default background, and erase-to-end leaves default-background cells. This verifies the default-style continuation case; restoring arbitrary nondefault live-stream SGR may need broader consideration.
Expected: restored history preserves captured colors without letting the final row's style bleed into live updates. A browser resize should not be needed to recover correct colors.
Thanks @ludovicfourrage, this is a clean report and the analysis holds. I checked it against master:
The visible-frame path closes every row it paints with \x1b[0m (truncatePaneLineByVisibleColumns, src/tmux-manager.ts:673), so a tab switch never leaks the last row's colors.
The full-history path returns normalizeScrollbackEol(trimmed) followed by formatCursorRestore(geometry) (src/tmux-manager.ts:4093) with no reset, and its no-geometry fallback at line 4079 has none either. capture-pane -e leaves the last cell's SGR open, so a nested tmux status bar on the bottom row (a remote or Docker case, or tmux run inside a shell session) hands its colors to the next live write that sets no SGR of its own, erase-to-end included.
The remote splice (joinScrollbackAndFrame) already resets between the remote scrollback and the frame, so the tail is the only gap.
Your correction is the right one. The reset goes before the cursor restore, which is only CUU, CR and CUF, so the caret still lands where it should. On restoring arbitrary live SGR: the visible path already hands the live stream over at the default style, so doing the same after a full-history replay makes the two paths agree rather than adding a new assumption.
One more site with the same shape, if you want it in the same change: the visible path's own no-geometry fallback, return normalizeScrollbackEol(buffer) at line 4102, also returns the raw capture without a reset.
A PR is welcome, with your browser regression (both branches) as the test plus a unit test on capturePaneBuffer that the returned string carries \x1b[0m ahead of the cursor move. If you'd rather not, say so and I'll pick it up for the next release.
Thanks for checking the analysis and flagging the visible-path fallback. Opened #632 covering all three sites, with the reset before cursor restoration.
The PR includes a unit test on the real capturePaneBuffer (mocked tmux IO) and the Chromium/xterm regression for both full-history branches, plus the visible no-geometry fallback. All three browser cases failed before the change and pass afterward: the status bar stays green while subsequent text and erased cells use the default background. Empty raw captures still return an empty string so the caller retains its recorded history.
135 focused tests, the three browser cases, typecheck, lint, formatting, and the browser-suite exclusion check pass.
Activity
Ark0N commentedon Oct 14, 2026
Thanks @ludovicfourrage, this is a clean report and the analysis holds. I checked it against master:
\x1b[0m(truncatePaneLineByVisibleColumns,src/tmux-manager.ts:673), so a tab switch never leaks the last row's colors.normalizeScrollbackEol(trimmed)followed byformatCursorRestore(geometry)(src/tmux-manager.ts:4093) with no reset, and its no-geometry fallback at line 4079 has none either.capture-pane -eleaves the last cell's SGR open, so a nested tmux status bar on the bottom row (a remote or Docker case, ortmuxrun inside a shell session) hands its colors to the next live write that sets no SGR of its own, erase-to-end included.joinScrollbackAndFrame) already resets between the remote scrollback and the frame, so the tail is the only gap.Your correction is the right one. The reset goes before the cursor restore, which is only CUU, CR and CUF, so the caret still lands where it should. On restoring arbitrary live SGR: the visible path already hands the live stream over at the default style, so doing the same after a full-history replay makes the two paths agree rather than adding a new assumption.
One more site with the same shape, if you want it in the same change: the visible path's own no-geometry fallback,
return normalizeScrollbackEol(buffer)at line 4102, also returns the raw capture without a reset.A PR is welcome, with your browser regression (both branches) as the test plus a unit test on
capturePaneBufferthat the returned string carries\x1b[0mahead of the cursor move. If you'd rather not, say so and I'll pick it up for the next release.ludovicfourrage commentedon Oct 14, 2026
Thanks for checking the analysis and flagging the visible-path fallback. Opened #632 covering all three sites, with the reset before cursor restoration.
The PR includes a unit test on the real
capturePaneBuffer(mocked tmux IO) and the Chromium/xterm regression for both full-history branches, plus the visible no-geometry fallback. All three browser cases failed before the change and pass afterward: the status bar stays green while subsequent text and erased cells use the default background. Empty raw captures still return an empty string so the caller retains its recorded history.135 focused tests, the three browser cases, typecheck, lint, formatting, and the browser-suite exclusion check pass.