Skip to content

Commit 8956288

Browse files
committed
fix(tools): Revert snapshot_pane delimiter to tab, keep defensive padding
why: CI exposed that tmux's display-message output C-escapes non-tab ASCII control characters. What I expected to be a single 0x1f byte arrived as the literal 4-character string "\037", producing a single joined parts[0] value that contained every embedded "\037". The first int() conversion then blew up with: ValueError: invalid literal for int() with base 10: '0\\0370\\03780\\03724\\0370\\037...' Six of seven CI tmux versions (3.2a, 3.3a, 3.4, 3.5, 3.6, master) escape on output; local tmux 3.6a does not. The "defense in depth" delimiter choice in the earlier snapshot_pane hardening commit traded a rare hazard (tabs in pane_title, which tmux's `select-pane -T` silently rejects anyway) for a guaranteed one (control-char escaping in display-message output) and broke every CI matrix run. The defensive padding remains the genuinely load-bearing part of the earlier hardening and is retained verbatim. what: - Switch _SEP in snapshot_pane from "\x1f" back to "\t". - Keep the defensive `(raw.split(_SEP) + [""] * 11)[:11]` padding so snapshot_pane still degrades gracefully when tmux emits fewer format fields than expected. - Update the inline comment to explain the real constraint: tabs survive display-message verbatim, other control chars do not, and tmux rejects tabs in pane_title so the tab-in-title risk is purely theoretical. - Update the monkeypatched fake_cmd in test_snapshot_pane_pads_short_display_message_output to split / rejoin on "\t" to match the new delimiter. Verified locally: all four tests that failed on CI (test_snapshot_pane, test_snapshot_pane_cursor_moves, test_snapshot_pane_pads_short_display_message_output, test_enter_and_exit_copy_mode) now pass.
1 parent d2f6ca0 commit 8956288

2 files changed

Lines changed: 13 additions & 7 deletions

File tree

src/libtmux_mcp/tools/pane_tools.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -670,11 +670,17 @@ def snapshot_pane(
670670
window_id=window_id,
671671
)
672672

673-
# Fetch all metadata in a single display-message call. Use the ASCII
674-
# Unit Separator (0x1f) as the field delimiter — it cannot appear in
675-
# normal terminal titles or paths, so tabs/newlines embedded in
676-
# pane_title or pane_current_path can't shift field indices.
677-
_SEP = "\x1f"
673+
# Fetch all metadata in a single display-message call. Use a tab as
674+
# the delimiter: tmux passes tabs through verbatim in
675+
# display-message output, whereas other ASCII control characters
676+
# (e.g. 0x1f / Unit Separator) get C-escaped to literal "\037"
677+
# strings on tmux >=3.2 / <3.6-rc, which corrupts parsing. Tabs in
678+
# pane_title are silently rejected by tmux's `select-pane -T`
679+
# input sanitizer, so the `\t` delimiter is safe against that
680+
# vector. Tabs in pane_current_path are legal on Linux but
681+
# vanishingly rare; the defensive padding below limits the blast
682+
# radius to a single truncated field rather than an IndexError.
683+
_SEP = "\t"
678684
fmt = _SEP.join(
679685
[
680686
"#{cursor_x}",

tests/test_pane_tools.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,8 +584,8 @@ def fake_cmd(self, cmd_name, *args, **kwargs): # type: ignore[no-untyped-def]
584584
# simulate an old tmux that dropped several unknown format
585585
# variables. Without defensive padding, parts[2..10] would
586586
# IndexError.
587-
parts = result.stdout[0].split("\x1f") if result.stdout else [""]
588-
result.stdout = ["\x1f".join(parts[:2])]
587+
parts = result.stdout[0].split("\t") if result.stdout else [""]
588+
result.stdout = ["\t".join(parts[:2])]
589589
return result
590590

591591
monkeypatch.setattr(mcp_pane.__class__, "cmd", fake_cmd)

0 commit comments

Comments
 (0)