Skip to content

psmux: the seam's detach/switch booleans are vacuously True — the parked-return honesty guarantee is tmux-only #317

Description

@dracic

Child of #288. Same root cause as #228 — psmux reports execution, not effect — but a different call path and a different consumer, so it does not fit under that issue's title or scope.

#228 is about the shell trailer: switch-client -t "$ret" || switch-client -l cannot reach its fallback because the first command exits 0. This is about the Python seam: TerminalMultiplexer.switch_client / detach_client return a boolean that tui.launch.return_attached_client consumes to decide whether to clear RETURN_OPTION. On psmux that boolean is vacuously True.

Verified in the psmux source at v3.3.7 (the version available() admits)

switch-client (src/main.rs, the "switch-client" | "switchc" arm): every flag is folded into a command string and the arm ends in

cmd.push('\n');
send_control(cmd)?;
return Ok(());

send_control returns Ok on the TCP round-trip, so ? propagates only a transport error. No form carries a server reply — -t included. Exit 0 whether or not any client moved.

detach-client (same file, the "detach-client" | "detach" arm): the only nonzero exit is an unreachable session server —

if send_control(server_cmd).is_err() {
    eprintln!("psmux: no session '{}'", session_for_routing);
    std::process::exit(1);
}
return Ok(());

— so it succeeds with zero clients attached. It also promotes a flag-less invocation to detach-all, which is what the backend issues:

let effective_all = detach_all || (t_target.is_none() && shell_cmd.is_none());

Post-tag: current main gives switch-client a response path (send_control_with_response), but only for -t. The -l fallback stays exit-0-regardless, and detach-client is unchanged — the arm is identical between v3.3.7 and 33ea109.

Why nothing is broken today

Latent, not live. psmux's per-window option channel is inert (#310), so return_attached_client reads an empty RETURN_OPTION and answers False before reaching either verb. The moment #310 / #315 revives that channel, this becomes reachable: the parked-return path will consume a True that means nothing, clear the return option, and report a hand-back that never happened — reintroducing #227's exact symptom on the platform the psmux backend exists for.

Impact when live

sweep._return_after_decisions journals sweep-returned-after-decisions, prints "✓ decisions recorded", and sets prompting = False — while the human is still attached and the client never moved. Every later decision then defers through the unattended path.

Candidate directions

  1. Version-gate the honesty claim — same shape as psmux pre-#483-fix rc-0 no-op: gate the version and document the fallback ceiling #228's proposed gate; the seam answers a typed "unknown" rather than True on builds that cannot report effect.
  2. Observe effect instead of trusting the exit code — e.g. compare list-clients before/after, at the cost of an extra round-trip on a path that runs once per decisions phase.
  3. Accept and contain — keep the vacuous True but make return_attached_client verify the option was actually consumed. Weakest, but backend-agnostic.

Nothing is actionable until #310 lands; filing now so the reviver of that channel inherits the constraint rather than rediscovering it.

Context

Surfaced by the review of #227 / PR #316, which widened detach_client from None to bool and made the tmux path honest (smoke-measured: detach-client exits 1 with no current client when nothing is attached, 0 with a client). The psmux divergence is recorded in the backend's degradation ledger there; this issue is its tracker.

Refs: #288, #228, #222, #310, #315, #227

Metadata

Metadata

Assignees

Labels

area:psmuxpsmux terminal-multiplexer backendbugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions