Skip to content

validate: mux.backend and mux.selection resolve the backend independently and can disagree #511

Description

@pbean

validate derives the multiplexer backend from two independent selections, so one report can name two different backends.

What happens

platform_preflight (src/bmad_loop/runsetup.py:478) resolves the backend twice:

  • runsetup.py:503get_multiplexer(), an @lru_cache(maxsize=1) over _select() (src/bmad_loop/adapters/multiplexer.py:618). This is the authoritative pick: it is the instance the run will actually drive. It reports as mux.backend.
  • runsetup.py:534detect_multiplexers(), which calls _select() a second time (multiplexer.py:655) and re-probes available() on every candidate. Its selected row feeds mux.selection (runsetup.py:576) and the host.win32-on-wsl-path warning's "<X> was selected" clause (runsetup.py:637).

_select steps 3–4 gate on _usable()available(), which shells out (tmux -V; psmux additionally needs pwsh). If that answer differs between the two calls — a transient fork failure, an antivirus lock, a binary appearing or disappearing — the two findings disagree inside one report, and host.win32-on-wsl-path asserts that a backend "was selected" which the process will not use. That check exists specifically to stop confidently-wrong output, so it is the worst place for it.

Why it is newly reachable

Pre-#485 the gate was if chosen and chosen.reason in ("env", "policy") — precisely the two _select() paths that return before any available() probe, so the second selection was deterministic and could not diverge. #485 un-gated it to if chosen:, which is correct for its purpose (platform-default is the reason #332 needed named) but exposes the three reasons that do depend on available().

Severity

Low: cosmetic in validate's own output, no in-repo consumer branches on the detail (documents.py only documents it; checks.py only registers the id), and it cannot change which backend a run drives.

Why the obvious fix is wrong

"Carry label forward instead of re-deriving from chosen" does not work: mux.backend reports a class name (type(backend).__name__TmuxMultiplexer, runsetup.py:504) while mux.selection reports a registry name (chosen.nametmux). They are different namespaces, so there is nothing to compare or substitute directly, and swapping one for the other would change the message wording and the detail value.

A correct fix wants a cached accessor exposing the full (instance, name, reason) triple that _select() already returns, so mux.backend and mux.selection share one authoritative selection. get_multiplexer() currently discards two thirds of it (return _select()[0]). That is a change to the multiplexer seam, which is why it was kept out of #485 — that PR's safety argument is that git diff over adapters/ is empty.

Found by Codex review on #485 (runsetup.py:579); triaged there and deliberately deferred rather than grown into that PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions