You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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)
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.
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.
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.
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.
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.
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.lambda platform: platform == "win32"(src/bmad_loop/adapters/multiplexer.py:372) and is the platform default only forwin32(multiplexer.py:334)._select()(multiplexer.py:501-565) readssys.platformand nothing else — noplatform.system(), noos.name, no uname//proc/versionsniffing. The onlyplatform.system()in the tree is display-only in the diagnose dump (src/bmad_loop/diagnostics.py:223).sys.platformis baked in at CPython build time, so a Linux interpreter — including every WSL interpreter — reportslinuxand 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/uvfrom PowerShell,bmad-loop.exeon the Windows path, or a.venvcreated by Windows uv) can be invoked from a WSL shell and runs on awin32interpreter. The user is "in WSL" in every way they can see — bash prompt,/home/...project, tmux installed — but the process reportswin32, 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.tomlcarrying[mux] backend = "psmux". Forced choices bypass both the platform predicate andavailable()(multiplexer.py:516-535), so a psmux pin made on a Windows machine applies verbatim inside WSL.initgitignores the file, but a repo initialized before that still has it committed — the exact case_warn_if_policy_trackedalready warns about (src/bmad_loop/install.py:795-818).Why the user can't see it
Only
bmad-loop muxnames the platform:selection: psmux (PsmuxMultiplexer) — platform default for win32(src/bmad_loop/runsetup.py:156,cli.py:408,434). Nothing else does.bmad-loop validateemits amux.selectionfinding only forenv/policyreasons (runsetup.py:247-248), so aplatform-defaultmis-pick produces no selection line at all. Text mode also never rendersdetail(src/bmad_loop/checks.py:150-166), so the wholemux.backends-detectedpayload — includingreason— is--json-only. On a WSL host with tmux installed, validate can printok: multiplexer PsmuxMultiplexer available (tmux 3.3.7)and stop there.bmad-loop diagnoseprintsos,os_release,python_version,multiplexer(diagnostics.py:218-228,573-580) — but notsys.platformand notsys.executable. The interpreter path is the smoking gun for case (a) and it is absent from the dump we ask bug reporters to attach..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/pwshon PATH —src/bmad_loop/adapters/psmux_backend.py:870-880): the firsthas-sessionraisesTmuxErrorout oftmux_base.py:114-123before the CLI ever launches; the run crashes at session ci: bump actions/checkout from 6 to 7 #1. Loud, and zero tokens.\\wsl.localhost\...UNC cwd):_window_alivefailures are deliberately swallowed as "don't know, not dead" (src/bmad_loop/adapters/generic.py:707-708,733-740,806-807), so sessions ride totimeout, andtimeout/stalled/crashedre-dispatch withtask.attempt += 1up tomax_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
muxoutput.Proposed fix (small, mostly diagnostics)
diagnose: addsys.platformandsys.executabletoEnvInfo(diagnostics.py:218-228) and the rendered dump.python: 3.12.4+os: Windows 10+multiplexer: PsmuxMultiplexerin a report titled "WSL" would then be self-evident.validate: emitmux.selectionfor every reason, not justenv/policy(runsetup.py:247-248) —ok: multiplexer selection platform default for win32is the one line that makes this self-service.sys.platform == "win32"and WSL markers are present (WSL_DISTRO_NAME/WSL_INTEROPin the env, or a\\wsl$\/\\wsl.localhost\cwd), surface avalidatewarning — "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.docs/setup-guide.mdunder the WSL section — inside WSL, install with the Linux interpreter, andbmad-loop muxshould sayplatform default for linux.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 awin32interpreter unknowingly, and that we never told them.Asks for the reporter
Plus how bmad-loop was installed (PowerShell vs WSL shell) and whether
.bmad-loop/policy.tomlis tracked by git.