Skip to content

WSL: a native-Windows interpreter reached through interop selects psmux, and nothing tells the user #332

Description

@pbean

Reported on Reddit: a Windows/WSL user setting up bmad-loop ends up on the psmux backend instead of tmux — i.e. the host is being treated as win32 — alongside heavy token consumption.

Source: https://www.reddit.com/r/BMAD_Method/comments/1v7eukn/bmadloop_just_devouring_tokens/ — reddit 403s automated fetches, so the thread below is summarized second-hand from the reporter relay, not quoted. Nothing here should be treated as confirmed reporter output; the asks at the bottom close that gap.

What is not possible

Backend selection has exactly one input: sys.platform.

  • psmux registers as lambda platform: platform == "win32" (src/bmad_loop/adapters/multiplexer.py:372) and is the platform default only for win32 (multiplexer.py:334).
  • _select() (multiplexer.py:501-565) reads sys.platform and nothing else — no platform.system(), no os.name, no uname//proc/version sniffing. The only platform.system() in the tree is display-only in the diagnose dump (src/bmad_loop/diagnostics.py:223).

sys.platform is baked in at CPython build time, so a Linux interpreter — including every WSL interpreter — reports linux and can never take the psmux branch. Docs promise exactly this (README.md:42, docs/setup-guide.md:36, docs/multiplexer-backends.md:49, docs/ROADMAP.md:33).

What is actually happening (two candidates)

(a) WSL interop running a native-Windows bmad-loop — the likely one. WSL appends the Windows PATH, so a bmad-loop installed under a Windows Python (pip/uv from PowerShell, bmad-loop.exe on the Windows path, or a .venv created by Windows uv) can be invoked from a WSL shell and runs on a win32 interpreter. The user is "in WSL" in every way they can see — bash prompt, /home/... project, tmux installed — but the process reports win32, takes the psmux platform default, and picks up the Windows process host and every other win32 seam (src/bmad_loop/process_host.py:384-385, gates.py:56, platform_util.py:86, verify.py:1508).

(b) A shared policy.toml carrying [mux] backend = "psmux". Forced choices bypass both the platform predicate and available() (multiplexer.py:516-535), so a psmux pin made on a Windows machine applies verbatim inside WSL. init gitignores the file, but a repo initialized before that still has it committed — the exact case _warn_if_policy_tracked already warns about (src/bmad_loop/install.py:795-818).

Why the user can't see it

Only bmad-loop mux names the platform: selection: psmux (PsmuxMultiplexer) — platform default for win32 (src/bmad_loop/runsetup.py:156, cli.py:408,434). Nothing else does.

  • bmad-loop validate emits a mux.selection finding only for env/policy reasons (runsetup.py:247-248), so a platform-default mis-pick produces no selection line at all. Text mode also never renders detail (src/bmad_loop/checks.py:150-166), so the whole mux.backends-detected payload — including reason — is --json-only. On a WSL host with tmux installed, validate can print ok: multiplexer PsmuxMultiplexer available (tmux 3.3.7) and stop there.
  • bmad-loop diagnose prints os, os_release, python_version, multiplexer (diagnostics.py:218-228, 573-580) — but not sys.platform and not sys.executable. The interpreter path is the smoking gun for case (a) and it is absent from the dump we ask bug reporters to attach.
  • The bug-report template's OS dropdown offers only macOS / Windows / Linux / Other (.github/ISSUE_TEMPLATE/bug-report.yaml), so "Windows via WSL" reports arrive ambiguous by construction.

Token burn — the link needs the reporter's output

Not automatic, and the two sub-cases differ:

  • psmux absent (no psmux/pwsh on PATH — src/bmad_loop/adapters/psmux_backend.py:870-880): the first has-session raises TmuxError out of tmux_base.py:114-123 before the CLI ever launches; the run crashes at session ci: bump actions/checkout from 6 to 7 #1. Loud, and zero tokens.
  • transport half-works (binary present, calls flaky — plausible for a Windows psmux driving a \\wsl.localhost\... UNC cwd): _window_alive failures are deliberately swallowed as "don't know, not dead" (src/bmad_loop/adapters/generic.py:707-708,733-740,806-807), so sessions ride to timeout, and timeout/stalled/crashed re-dispatch with task.attempt += 1 up to max_dev_attempts (engine.py:1168, 2840-2841). If the coding CLI does start each time, that is N full sessions of real tokens per story.

So "devouring tokens" is consistent with (a) + a half-working transport, but the causal chain is a hypothesis until we see the reporter's mux output.

Proposed fix (small, mostly diagnostics)

  1. diagnose: add sys.platform and sys.executable to EnvInfo (diagnostics.py:218-228) and the rendered dump. python: 3.12.4 + os: Windows 10 + multiplexer: PsmuxMultiplexer in a report titled "WSL" would then be self-evident.
  2. validate: emit mux.selection for every reason, not just env/policy (runsetup.py:247-248) — ok: multiplexer selection platform default for win32 is the one line that makes this self-service.
  3. Warn on the interop mismatch: when sys.platform == "win32" and WSL markers are present (WSL_DISTRO_NAME / WSL_INTEROP in the env, or a \\wsl$\ / \\wsl.localhost\ cwd), surface a validate warning — "running the native-Windows build from a WSL shell; install bmad-loop with the WSL/Linux Python to get tmux". Both markers need verifying against a real WSL interop launch before we rely on them; the UNC-cwd check is the one we can assert from the path alone.
  4. Docs: one line in docs/setup-guide.md under the WSL section — inside WSL, install with the Linux interpreter, and bmad-loop mux should say platform default for linux.
  5. Add a "Windows (WSL)" option to the bug-report OS dropdown.

Deliberately not proposing: changing selection to sniff for WSL. If the interpreter is win32, psmux is the right backend for that interpreter — the bug is that the user got a win32 interpreter unknowingly, and that we never told them.

Asks for the reporter

bmad-loop mux                  # the 'selection: ... — platform default for X' line
bmad-loop --version
python -c "import sys; print(sys.platform, sys.executable)"
grep -A2 '\[mux\]' .bmad-loop/policy.toml
echo $BMAD_LOOP_MUX_BACKEND

Plus how bmad-loop was installed (PowerShell vs WSL shell) and whether .bmad-loop/policy.toml is tracked by git.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:psmuxpsmux terminal-multiplexer backendbugSomething isn't workingdocumentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions