Regroup listing records on the field separator, not newlines - #752
Open
tony wants to merge 1 commit into
Open
Conversation
A pane whose `pane_current_path` contained a newline made `Server.panes` and `Server.windows` raise `ValueError: zip() argument 2 is shorter than argument 1` for the entire server, healthy panes included. `fetch_objs` iterated stdout one line per object, so a value containing a newline split its record across two lines and each fragment reached `parse_output` with too few values. Every pane row carries `pane_current_path` and every pane-targeting lookup enumerates panes, so one directory took out resolution for all of them. The blast radius also moved with the active pane, because session and window rows resolve `pane_*` against it — the same server appeared to work or fail as the user switched panes. Regrouping on the field separator is exact rather than merely better: the `-F` template terminates every field with one, so a record holds exactly `len(fields)` separators and a newline is never among them. Nothing is split on newlines any more, so a value may contain any number of them, in any position. The newline that terminated the previous record survives the rejoin glued to the next record's first value and is stripped as the delimiter it is. Regrouping also makes a forged separator detectable: a value count that is not a whole number of records now raises a `LibTmuxException` naming the cause instead of surfacing a `zip()` message. Reported against libtmux-mcp, where an agent hit it by cd-ing a pane into such a directory and then could not repair it through the MCP, because every tool that could have moved the pane needed the same enumeration.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #752 +/- ##
==========================================
+ Coverage 52.37% 52.53% +0.16%
==========================================
Files 26 26
Lines 3729 3746 +17
Branches 747 752 +5
==========================================
+ Hits 1953 1968 +15
- Misses 1472 1473 +1
- Partials 304 305 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
tony
added a commit
to tmux-python/libtmux-mcp
that referenced
this pull request
Aug 25, 2026
A pane whose current directory contains a newline makes libtmux fail to parse `-F` output, and because every pane lookup enumerates panes, the whole server stops resolving — healthy panes included. It reached the agent as `Unexpected error: ValueError: zip() argument 2 is shorter than argument 1`, logged at ERROR, naming nothing it could act on. The agent could not repair it through the MCP either: every tool that could have moved the pane out needed the same enumeration. It is now an expected failure that names the cause, says the blast radius is server-wide rather than one pane, and gives the command that locates the offender. Matched on the message because the raise site is a stdlib `zip` with no dedicated exception type. The parse itself is fixed upstream in tmux-python/libtmux#752, but this diagnosis is kept rather than deferred: the floor is `libtmux>=0.62.0` and the installed version is not this package's to choose. `exc.PaneNotFound` prefixes its own message and the mapper prefixed it again, so the most frequently hit error in the server read `Pane not found: Pane not found: %9999`.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A pane whose
pane_current_pathcontains a newline — a directory whose name has one — makesServer.panesandServer.windowsraiseValueError: zip() argument 2 is shorter than argument 1for the entire server, healthy panes included.Reproduce
Cause
fetch_objsiterated stdout one line per object:tmux writes one record per line, but a value containing a newline splits that record across two output lines. Each fragment then reaches
parse_outputwith fewer values than the template has fields, and itszip(..., strict=True)raises.Two properties make this worse than a single bad pane:
pane_current_path, and every pane-targeting lookup enumerates panes, so one directory breaks resolution for all of them.pane_*against the session's active pane, so the same server appears to work or fail depending on which pane the user last selected — which is whysessionsabove may or may not raise.Fix
Records are regrouped on the field separator instead of on newlines. This is exact rather than merely better: the
-Ftemplate fromget_output_formatterminates every field with a separator, so one record holds exactlylen(fields)separators and a newline is never one of them. Nothing is split on newlines any more, so a value may contain any number of them, in any position. The newline that terminated the previous record survives the rejoin glued to the next record's first value, and is stripped as the delimiter it is.Regrouping also makes a forged separator detectable. A value that contains the separator itself previously corrupted the parse silently; a value count that is not a whole number of records now raises a
LibTmuxExceptionnaming the cause.Tests
_split_recordsis covered for a newline in the first, middle, and last field, consecutive newlines, a poisoned record between clean ones, empty values, zero records, and the forged-separator error.Verified end to end against a real poisoned server:
sessions,windows, andpanesall enumerate, and the value round-trips exactly as'/tmp/evil\ndir'.Provenance
Found while auditing
libtmux-mcp, where an agent thatcd-ed a pane into such a directory could not repair it through the MCP at all — every tool that could have moved the pane back needed the same enumeration.tests/test_server.py::test_new_session_shell_envfails in my local environment before and after this change (new-session: command too long, caused by an unusually large environment being passed as-eflags); it is unrelated.