Skip to content

Two of three nudge sites are unguarded: send_text raises on a dead window and the exception escapes the session loop #504

Description

@dracic

Summary

send_text raises on a nonzero exit — BaseTmuxBackend.send_text (adapters/tmux_base.py:328-331)
goes through _tmux_run(check=True), and TmuxError subclasses MultiplexerError
(tmux_base.py:44, multiplexer.py:42). Three call sites nudge an idle session, and only one of them
guards that:

site call guarded
generic.py:676 budget nudge self.send_text(handle, BUDGET_NUDGE_TEXT) yesexcept MultiplexerError, comment: "a dead/hung window can't take the nudge"
generic.py:782 stall nudge self.send_text(handle, STALL_NUDGE_TEXT) no
generic.py:857 generic nudge self.send_text(handle, NUDGE_TEXT) no

Nothing upstack catches it either: grep -n "TmuxError\|MuxError" src/bmad_loop/engine.py is empty, and
the three except Exception blocks in generic.py (:1061, :1067, :1125) are in the teardown
path, not the poll loop. So the exception escapes the session loop.

Why it is reachable

A nudge fires precisely when the session has gone quiet, and the window can die inside the same tick.
The code says so itself, in the comment immediately after the unguarded stall nudge
(generic.py:786-789):

Re-probe liveness before finalizing: this return exits the loop, so a hard death (no SessionEnd) in
the gap since the top-of-tick probe would otherwise never be caught.

That re-probe sits on the finalize branch, after the nudge branch has already run. On the tick where
the stall deadline expires and the window died since the top-of-tick probe, the nudge is attempted
against a dead target first.

Platform status

  • tmux (Linux/macOS): live today. send-keys to an unresolvable target already exits 1:
    $ tmux send-keys -t S:nosuchwindow 'echo x' Enter
    can't find window: nosuchwindow
    rc=1
    So an unguarded nudge raises out of the loop, and the run dies with a traceback instead of taking the
    crash path that honors the session's artifact.
  • psmux 3.3.7: masked, and masked by a worse bug. send-keys to a stale target exits 0 there and
    the keystrokes are typed into whichever window is active — kill-pane/rename-window/send-keys/capture-pane with an unresolvable -t target silently operate on the ACTIVE window (rc=0) instead of erroring psmux/psmux#545, which we filed and which
    is now fixed on psmux master (194fac0, send-keys is in the new validator's command list). The
    moment 3.3.8 is admitted, psmux joins tmux and this path starts raising.

So the same admission that removes a silent-misroute hazard converts it into an unhandled exception on
two of three sites.

Ask

Give the stall and generic nudge sites the guard the budget site already has, and decide what a failed
nudge means: the budget site treats it as "the window is gone, stop nudging" and continues to the
liveness re-probe, which looks right for all three.

Worth checking in the same pass whether any other MultiplexerError-raising seam call sits unguarded
inside the poll loop — send_text is the one that runs because the session looks dead, so it is the
most exposed, but the audit is cheap while someone is in there.

Found during the psmux pre-3.3.8 bughunt, while checking which adapter tolerance points the upstream
target-validation fix would harden

Refs: psmux/psmux#545, #222

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions