fix(Pane,Window): Resolve point lookups through tmux#713
Open
tony wants to merge 2 commits into
Open
Conversation
why: A window can be linked into several sessions at once. The four
point lookups -- Pane.refresh, Pane.from_pane_id, Window.refresh,
Window.from_window_id -- listed every object on the server and picked
the last row matching the id. tmux orders that listing by session name,
so libtmux answered "which session is this in?" with whichever session
happened to sort last, and the answer changed if you renamed a session.
It also disagreed with tmux, which resolves the same id to the session
it would itself act on.
Naming the object with -t hands the question to tmux's cmd_find, which
picks the most recently active session holding the window. One row comes
back instead of a server-wide scan, so the lookups also get cheaper.
The catch: a live server rejects an unknown -t target on stderr rather
than by returning an empty listing, so fetch_obj would have stopped
raising TmuxObjectDoesNotExist entirely. Classifying that stderr also
separates "the object is gone" from "the server is gone", which the two
failures had no way to distinguish before.
what:
- Add neo._is_target_not_found_error, mirroring
server._is_daemon_not_up_error, and map tmux's "can't find <kind>"
back to TmuxObjectDoesNotExist inside fetch_obj
- Switch the four point lookups from list_extra_args=("-a",) to
("-t", <id>)
- Cover the classifier with a parametrized NamedTuple fixture, and the
resolution itself against a window linked into two sessions whose
name order and activity order disagree
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #713 +/- ##
==========================================
- Coverage 51.86% 51.85% -0.02%
==========================================
Files 25 25
Lines 3638 3645 +7
Branches 733 735 +2
==========================================
+ Hits 1887 1890 +3
- Misses 1445 1449 +4
Partials 306 306 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
why: The answer to "which session is this window in?" changes for anyone with a linked window, and the exception a failed lookup raises is now narrower. Both are visible from outside. what: - Add a Fixes entry for #713
This was referenced Jul 11, 2026
Closed
Open
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.
Fixes #710.
The bug
A window can be linked into several sessions at once. Ask libtmux which session such a window (or a pane inside it) is in, and the answer depends on what the sessions are called:
Rename
zzz-guesttoaaa-guestand the same call answersaaa-homeinstead. Nothing about the window changed.The four point lookups —
Pane.refresh,Pane.from_pane_id,Window.refresh,Window.from_window_id— passed-atolist-panes/list-windows, listing every object on the server. tmux emits a linked window once per holding session, ordered by session name (its session tree is keyed bystrcmp), andneo.fetch_objscans that listing keeping the last row that matches the id. So "which session?" resolved to whichever session sorted last alphabetically.It also disagreed with tmux.
tmux display-message -t @0 '#{session_id}'resolves the same window throughcmd_find, which picks the most recently active session holding it. libtmux and tmux gave different answers for the same id.The fix
Name the object with
-tand let tmux resolve it.tmux's
cmd_find_targetroutes a target to a slot by sigil, not by the command's declared target type (cmd-find.c:1078-1083):$→session,@→window,%→pane. Solist-panes -t %IDroutes%IDto the pane slot even thoughlist-panesdeclares a window target, and resolves upward throughcmd_find_best_session_with_window()— the same function tmux uses for every-tyou type. One row comes back, stamped with the session tmux itself would act on.This is byte-identical in every tmux CI covers (
3.2a→3.7b), and libtmux already ships-ton these commands for same-type targets (Window.panes,Session.windows).Point lookups also get cheaper:
-tscans one window's panes or one session's windows instead of the whole server.Server.panes/Server.windows/Server.sessionskeep-a— a server-wide listing is what they're for.The catch, and what it bought
A live server rejects an unknown
-ttarget on stderr (can't find pane: %99) rather than by returning an empty listing.raise_if_stderrturns that into aLibTmuxException, sofetch_objwould have stopped raisingTmuxObjectDoesNotExistaltogether — andTmuxObjectDoesNotExistextendsObjectDoesNotExist, notLibTmuxException, so nothing downstream would have caught it.neo._is_target_not_found_errorclassifies that stderr, mirroring the existingserver._is_daemon_not_up_error. Between them they answer a question the two failures previously had no way to distinguish: is the object gone, or is the server gone? A missing object staysTmuxObjectDoesNotExist; a dead daemon, a missing socket or a permission error stayLibTmuxException.Behaviour change
For a window in exactly one session — the overwhelmingly common case — nothing changes.
For a linked window,
refresh()/from_pane_id()/from_window_id()now report tmux's canonical session instead of the alphabetically-last one. That is the fix. Note the consequence: because tmux picks by activity, the answer can now change over time as focus moves between the sessions holding the window. That is tmux's own semantics, and it is what every-tcommand already does.Tests
tests/test_resolution.pybuilds a window linked into two sessions whose name order and activity order disagree (zzz-oldsorts last butaaa-recentis active), so a resolver that reads the last row of an-alisting cannot pass by luck. Both linked-window tests fail onmasterand pass here.The other five are guards for what the
-tswitch could have broken, and pass onmastertoo:Paneheld across amove-windowstill finds its session (Pane.sessionresolves throughPane.window, which re-queries — answering from the pane's cachedsession_idwould be one subprocess cheaper and would break here, because tmux destroys a session when its last window leaves)refresh()on a killed window/pane still raisesTmuxObjectDoesNotExistTmuxObjectDoesNotExist; a dead server is nottests/test_neo.pycovers the classifier against real tmux stderr for both kinds of failure.Verification
ruff·ruff format·mypy·pytest --reruns 0(1361 passed) ·just build-docs— all green.