fix(tui): record session-qualified parked-window return target#225
Conversation
An interactive attach recorded the client's origin as a bare pane id (%N) and replayed it as switch-client -t %N from inside the control session. Sound under tmux's one-server model, but on psmux (one server per session, upstream-final per psmux/psmux#483) a bare id is session-local: at best unresolvable, at worst colliding with a real control-session pane and landing the client on the wrong one with exit 0, past the switch-client -l fallback. The recording seam now emits =session:%N; the replay sides (POSIX and pwsh trailers, switch_client, return_attached_client) treat the value as an opaque target and are unchanged. If only the session-name probe fails, recording degrades to the bare pane id rather than detach, so a client that is demonstrably inside the multiplexer is never detached. tmux resolves the qualified form to the same pane it always did (verified on 3.4); psmux releases carrying the psmux/psmux#483 fix resolve it cross-server. Closes bmad-code-org#221
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
🚧 Files skipped from review as they are similar to previous changes (4)
WalkthroughAttach return handling now uses backend-composed targets, including session-qualified psmux targets such as ChangesSession-qualified return targets
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant AttachFlow
participant Launch
participant ControlWindow
participant MultiplexerClient
AttachFlow->>Launch: current_return_target()
Launch-->>AttachFlow: qualified or bare target
AttachFlow->>ControlWindow: set_return_pane(target)
ControlWindow->>MultiplexerClient: switch-client -t target
MultiplexerClient-->>ControlWindow: switch result or -l fallback
Possibly related issues
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Ran a multi-aspect review of this branch (general correctness, test coverage, comment accuracy, silent-failure audit). Verdict: the core change is sound — all degradation branches of One finding was addressed in a0ec7a1:
Verified safe (no action): session names cannot contain Two pre-existing adjacent issues surfaced by the audit are filed separately as follow-ups (ignored
|
current_return_target hand-assembled =session:%N, violating the seam contract the adapter guide documents (core never formats the grammar itself) and bypassing backend target() overrides. Route it through mux.target(session, pane) — byte-identical output for the tmux family — and note in the guide that this one caller carries a pane id in the window slot. Also tighten the surrounding comments: the RETURN_OPTION block now names the bare-%N degradation, and the parked-trailer comment distinguishes the option name from its recorded value.
112f76a to
a0ec7a1
Compare
|
@CodeRabbit review |
✅ Action performedReview finished.
|
pbean
left a comment
There was a problem hiding this comment.
Ran this through a full review with live verification: scratch tmux server with a real pty-attached client, tmux source at 3.4 and master, psmux cli.rs at master, and the herdr adapter source. Verdict: the recording-side structure is right, but =session:%N regresses the tmux path this PR promises to leave intact, and breaks the herdr adapter's return hop. Since maintainer edits are enabled I'll push a fix commit onto this branch (backend-owned composition — sketch at the end) rather than bounce it; review of that commit welcome.
1. tmux rejects =session:%N at the replay verb
$ tmux -V
tmux 3.7b
# scratch -L server: sessions ctl + main, real client attached to ctl via a pty
$ tmux -L S switch-client -t "=main:%1"
can't find window: %1 # rc=1, client does not move
$ tmux -L S switch-client -t "%1"
# rc=0, client lands on main:%1 (the pre-PR form)
Source-confirmed in 3.4 and master (cmd-find.c): with a colon and no period, cmd_find_target puts the post-colon part in the window slot unconditionally — the find type never matters there — and cmd_find_get_window_with_session has no % handling at all: it tries @ ids, +/- offsets, !/^/$, numeric index, then exact/prefix/fnmatch name matching, then returns -1 → no_window: → can't find window. The pane-id checks exist only in the pane helpers (reached via a .-separated pane part or the no-separator branch).
Consequence: every parked-window return on tmux now exits 1 and rides the switch-client -l fallback — origin session usually recovered, exact-pane return gone — and the client hard-strands in the control session whenever -l has nothing to fall back to (e.g. the client was attached directly into the ctl session, so it has no last session).
Why the PR's tmux 3.4 verification passed anyway — two independent false positives:
display-message -p -t '=main:%N' '#{pane_id}'answers the right pane, but not because the target resolves. Its target is declared{ 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, andcmd_find_target'serror:path under CANFAILreturn (0)with the partially-filled state: the session half already resolved, andcmd_find_get_window_with_sessionseedsfs->wl = fs->s->curwbefore failing. So the answer is "the session's current pane" — indistinguishable from correct resolution precisely when the probed pane is that session's active pane, i.e. exactly the by-hand test from your own pane.switch-client(flags = 0) takes the strictreturn (-1)and errors. Reproduced both against the same server and target string.- End-to-end, the
-lfallback usually lands near the right place (the origin session's curw is where the user left), masking the rc=1.
CI stayed green because every touched test scripts subprocess.run with rc=0 — nothing exercises real target resolution.
2. herdr's return hop breaks
herdr's current_pane_id() is a native id (w1:p1). This branch records target(label, "w1:p1") = =label:w1:p1; herdr switch_client → _parse_target → multiplexer.parse_target yields ("label", "w1:p1") and then looks for a tab labeled w1:p1 → None → silent False. Today's bare native id passes parse_target untouched (no leading =) and works. The guide note added here ("a backend … must tolerate a pane id in the window slot") is a contract change the shipped herdr adapter does not satisfy.
3. No uniform composition exists
=sess:%N— precise on psmux (parse_target: post-colon%Nsetspane_is_id), hard error on tmux (above).=sess:.%N— precise on tmux (verified live: rc=0, exact pane — the empty window slot routes%Nthroughcmd_find_get_pane_with_session, which does handle ids), but psmux silently drops the pane:.%Ngoes down the dot-split path and"%N".parse::<usize>()fails, leaving session-only.- bare
%N— precise on tmux and herdr, broken on psmux (#221 itself).
So the issue's constraint — "a single seam-level recording change beats per-backend branching if the form parses on both" — fails its own premise. The composition has to be backend-owned.
Fix being pushed
The recorded value is written to and replayed from the same backend's server, so the backend composes it: TerminalMultiplexer.current_return_target() gains a concrete default (self.current_pane_id() or None) — tmux and herdr byte-identical to pre-PR behavior with zero changes on their side — and PsmuxMultiplexer overrides it to emit =sess:%N (the form your psmux source read verified), degrading to bare %N when the session probe fails or answers with a : in the name (unchanged rationale: a resolvable own pane means we're inside the mux, so None→detach would strand the client). launch.current_return_target() becomes a thin delegate and the replay sides stay fully opaque — which preserves everything this PR got right: the rename, the empty-probe degradation guards and their tests, and the RETURN_DETACH non-collision reasoning.
The seam-uniform =session:%N recording regressed both non-psmux backends: tmux's window resolver has no pane-id handling, so the qualified form errors at switch-client (can't find window: %N) and every return degrades to the imprecise switch-client -l fallback — verified live (tmux 3.7b) and in 3.4/master cmd-find.c — while herdr's native w1:p1 pane id landed in the window slot and misresolved as a tab label. No single form parses everywhere (tmux's =sess:.%N dot form silently drops the pane on psmux's parse_target), so composition moves to where the value is recorded and replayed anyway — the backend: TerminalMultiplexer.current_return_target() defaults to the bare native pane id (tmux and native-id backends byte-identical to pre-bmad-code-org#225 behavior), and the psmux override emits =session:%N, degrading to the bare id when the session probe fails, answers empty, or answers a name the grammar cannot carry. Replay sides stay opaque; the launch helper becomes a thin delegate.
|
Pushed 0a427e5 implementing the backend-owned composition from the review, on top of your two commits. What it does:
Verification: full suite 2681 passed / 1 known skip; |
|
@CodeRabbit review |
✅ Action performedReview finished.
|
Summary
An interactive attach recorded the client's origin as a bare pane id (
%N) and replayed it asswitch-client -t %Nfrom inside the control session. Sound under tmux's one-server model, but on psmux (one server per session, upstream-final per psmux/psmux#483) a bare id is session-local: at best unresolvable, at worst colliding with a real control-session pane and landing the client on the wrong one with exit 0 — past theswitch-client -lfallback.This is candidate B from the issue thread: the recording seam (
tui/launch.py) now emits=session:%Nwhen both probes succeed, retaining bare%Nonly if the session-name probe fails (a resolvable own pane means the client IS inside the multiplexer, so recordingdetachwould strand it). An rc-0-but-empty pane probe is rejected before composing. The replay sides — POSIX and pwsh trailers,switch_client,return_attached_client— treat the value as an opaque target and are unchanged beyond comment accuracy.RETURN_DETACHstays collision-free.Verification
=sess:%Nparses and resolves to the exact pane;switch-clientaccepts the form. tmux resolves the qualified form to the same pane a bare id always reached.Closes #221
Summary by CodeRabbit