Bug
link-window puts one window in several sessions at once, and the pane inside it then genuinely belongs to all of them. Asked which session holds that pane, libtmux answers with whichever session sorts last by name — so renaming a session changes the answer, and libtmux disagrees with tmux itself.
Root cause
Two facts meet.
1. tmux lists sessions in name order. Sessions live in an RB-tree keyed by strcmp of the session name (session.c#L41-L45), so list-panes -a / list-windows -a walk sessions alphabetically. A linked window's pane is therefore emitted once per session, in name order.
2. fetch_obj keeps the last matching row. It iterates every row and overwrites, with no break (neo.py#L793-L796):
obj = None
for _obj in obj_formatters_filtered:
if _obj.get(obj_key) == obj_id:
obj = _obj
So Window.from_window_id hands back a Window whose identity is right but whose session_id field is whichever session came last alphabetically. Window.session then resolves that id, and Pane.session (return self.window.session) inherits it.
tmux itself does not answer this way
tmux has a deliberate rule. cmd_find_best_session_with_window() collects every session holding the window and then picks the best one (cmd-find.c#L176-L197), where "best" is the most recently active session, optionally preferring an unattached one (cmd-find.c#L134-L148):
if (flags & CMD_FIND_PREFER_UNATTACHED) { ... }
return (timercmp(&s->activity_time, &than->activity_time, >));
So tmux answers "the session you were most recently using". libtmux answers "the session whose name sorts last". These are different answers, and libtmux's is not a choice anyone made.
Reproduction
Two sessions, named so that the alphabetically last one is not the most recently active one, sharing a linked window:
srv = Server(socket_name="linkwin")
zzz = srv.new_session(session_name="zzz-old") # older activity
aaa = srv.new_session(session_name="aaa-recent") # most recent activity
w = aaa.new_window(window_name="shared", attach=False)
pane = w.active_pane
srv.cmd("link-window", "-s", str(w.window_id), "-t", f"{zzz.session_id}:")
srv.cmd("select-window", "-t", str(w.window_id)) # aaa is now clearly the active one
pane.session.session_name
srv.cmd("display-message", "-p", "-t", str(pane.pane_id), "#{session_name}").stdout[0]
Observed:
the pane is listed once per session, in session-NAME order:
aaa-recent|$1|%2
zzz-old|$0|%2
libtmux pane.session -> 'zzz-old' (fetch_obj keeps the LAST row)
tmux display-message -t % -> 'aaa-recent' (cmd_find: most recent activity)
Observed: libtmux and tmux disagree.
Expected: the same session tmux would resolve — or, at minimum, an answer that does not change when a session is renamed.
The same applies to Pane.window: the returned Window is the right window, but its session_id field is arbitrary, which is what makes Window.session wrong downstream.
Suggested fix
Ask tmux rather than scanning the listing. display-message -p -t <pane_id> '#{session_id}' runs through cmd_find, so it returns tmux's own best-session answer for free, in one targeted call. That is both correct and cheaper than the current list-panes -a scan.
Failing that, fetch_obj should at least stop silently picking a row when several match. Today an ambiguous lookup is indistinguishable from an unambiguous one; a caller cannot even discover that their window is linked. Options: return the first row (still arbitrary, but stable under renames), or surface the ambiguity so a caller can resolve it.
Note this is not fixable by looking harder at the row data: for a linked window, the server holds several equally true answers, and only a policy can choose between them. tmux has one; libtmux currently has an accident.
Related
Environment
- libtmux: v0.61.0 (
neo.py, pane.py, window.py unchanged since the tag)
- tmux: 3.7b
Bug
link-windowputs one window in several sessions at once, and the pane inside it then genuinely belongs to all of them. Asked which session holds that pane, libtmux answers with whichever session sorts last by name — so renaming a session changes the answer, and libtmux disagrees with tmux itself.Root cause
Two facts meet.
1. tmux lists sessions in name order. Sessions live in an RB-tree keyed by
strcmpof the session name (session.c#L41-L45), solist-panes -a/list-windows -awalk sessions alphabetically. A linked window's pane is therefore emitted once per session, in name order.2.
fetch_objkeeps the last matching row. It iterates every row and overwrites, with nobreak(neo.py#L793-L796):So
Window.from_window_idhands back aWindowwhose identity is right but whosesession_idfield is whichever session came last alphabetically.Window.sessionthen resolves that id, andPane.session(return self.window.session) inherits it.tmux itself does not answer this way
tmux has a deliberate rule.
cmd_find_best_session_with_window()collects every session holding the window and then picks the best one (cmd-find.c#L176-L197), where "best" is the most recently active session, optionally preferring an unattached one (cmd-find.c#L134-L148):So tmux answers "the session you were most recently using". libtmux answers "the session whose name sorts last". These are different answers, and libtmux's is not a choice anyone made.
Reproduction
Two sessions, named so that the alphabetically last one is not the most recently active one, sharing a linked window:
Observed:
Observed: libtmux and tmux disagree.
Expected: the same session tmux would resolve — or, at minimum, an answer that does not change when a session is renamed.
The same applies to
Pane.window: the returnedWindowis the right window, but itssession_idfield is arbitrary, which is what makesWindow.sessionwrong downstream.Suggested fix
Ask tmux rather than scanning the listing.
display-message -p -t <pane_id> '#{session_id}'runs throughcmd_find, so it returns tmux's own best-session answer for free, in one targeted call. That is both correct and cheaper than the currentlist-panes -ascan.Failing that,
fetch_objshould at least stop silently picking a row when several match. Today an ambiguous lookup is indistinguishable from an unambiguous one; a caller cannot even discover that their window is linked. Options: return the first row (still arbitrary, but stable under renames), or surface the ambiguity so a caller can resolve it.Note this is not fixable by looking harder at the row data: for a linked window, the server holds several equally true answers, and only a policy can choose between them. tmux has one; libtmux currently has an accident.
Related
Session.from_env()breaks the tie with the session id frozen into$TMUX, which records the session the process was spawned under. That works because the caller has extra information about itself. APaneyou fetched for somebody else has no such tiebreaker, which is why this needs its own answer.Environment
neo.py,pane.py,window.pyunchanged since the tag)