Skip to content

feat: make the wmux CLI work out of the box in agent shells (PATH cli-bin) - #75

Merged
amirlehmam merged 4 commits into
amirlehmam:masterfrom
tawman:feature/wmux-cli-on-path
Jul 7, 2026
Merged

feat: make the wmux CLI work out of the box in agent shells (PATH cli-bin)#75
amirlehmam merged 4 commits into
amirlehmam:masterfrom
tawman:feature/wmux-cli-on-path

Conversation

@tawman

@tawman tawman commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

The problem we hit

We use wmux heavily for Claude Code multi-agent orchestration — it's become our daily driver for exactly the workflow the README describes. But on a fresh install, orchestration silently degrades: spawn-agents.sh prints "WARNING: wmux not found in PATH" and falls back to subagent mode, and the coordinator's own wmux calls fail. The only way we could make it work as-is was hand-writing shims into ~/.local/bin and adding that to PATH on every machine.

Root cause

The orchestrator (and Claude Code itself) invokes bare wmux from non-interactive shells — Claude Code's Bash tool and the plugin's hook scripts run bash -c, not an interactive pane shell. wmux currently exposes the CLI two ways, and neither reaches that context:

  1. $WMUX_CLI — injected into every spawned shell's env, but nothing that calls bare wmux reads it.
  2. The wmux shell function — dot-sourced by the shell-integration scripts into the pane's interactive shell only. Shell functions don't propagate to bash -c children.

(You can't fix it by putting the install folder on PATH either: wmux.exe shadows any wmux.cmd via PATHEXT ordering, so wmux <command> would launch the GUI.)

The fix

PtyManager prepends a bundled cli-bin/ directory — containing tiny wmux (bash) and wmux.cmd (cmd) shims that just run node "$WMUX_CLI" — to PATH in the env it already builds for every spawned shell. PATH inherits down the entire process tree regardless of shell/login/interactive state, which is exactly what the shell function can't do. So bare wmux resolves everywhere: Claude Code's Bash tool, orchestrator hooks, cmd, PowerShell, Git Bash.

  • No PATHEXT collision — cli-bin/ contains no wmux.exe.
  • No behavior change in panes — the interactive wmux function still shadows the PATH shim (function > PATH).
  • Instance-correct — the shims prefer $WMUX_CLI/$WMUX_PIPE from the spawning instance.
  • Zero user setup: unzip → run wmux.exe → orchestration works.

Changes:

  • src/main/pty-manager.tsgetCliBinPath() + case-insensitive PATH prepend (the Windows env key is Path).
  • src/cli-bin/{wmux,wmux.cmd} — the shims; .gitattributes forces LF on the bash one.
  • electron-builder.json — ship src/cli-binresources/cli-bin via extraResources.
  • resources/wmux-orchestrator/scripts/wmux-resolve.sh (+ sourced from orchestration-state.sh / detect-wmux.sh) — a defense-in-depth fallback for the bundled plugin: if wmux still isn't on PATH (older wmux), it defines wmux() { node "$WMUX_CLI" "$@"; }, which also makes the existing command -v wmux guards pass. We're submitting the same fallback to the standalone wmux-orchestrator repo so plugin users on current wmux releases get working orchestration immediately.

Verification

  • With cli-bin prepended and our ~/.local/bin shims deleted, a non-interactive bash -c resolves wmux to the cli-bin shim and wmux pingpong via $WMUX_CLI.
  • With wmux off PATH entirely, sourcing wmux-resolve.sh makes detect-wmux.sh report available and wmux pingpong.
  • Running this build daily: real Claude Code orchestration spawns panes, hooks update the dashboard, and the coordinator drives wmux — with no shims installed anywhere.
  • build:main + vite build clean; all tests pass (158/158).

Also: orchestration lessons learned, folded into the distributed skills

We have been running heavy multi-agent Claude Code orchestrations on wmux, and this PR also contributes that operating knowledge back into the distributed skills so they match how orchestration actually behaves:

  • skills/orchestrate Phase 7 (monitoring) corrected — the completion signal is the agent's result file, not process exit: launch-agent.js runs agents interactively, so a finished agent idles at its TUI and never exits, and the previous "poll wmux agent list for exited" loop never terminates. The skill now polls result files, and documents that the coordinator owns state.json (Claude Code Stop/SubagentStop hooks don't fire for wmux-spawned agents) using the status vocabulary the sidebar actually counts (exited for agents, complete for waves/run).
  • Consistent agent ids — bare ids ("a", not "agent-a"): the scripts prefix agent- themselves, so the old schema example produced double-prefixed prompt/result filenames the launcher couldn't find.
  • Windows reliability notes — align TMPDIR with the temp dir the sidebar watches (Git Bash), forward-slash paths in state.json (backslashes are invalid JSON escapes and freeze the cockpit), retry reads that race the app's 1 s poller.
  • Pane hygiene — start each run from a single coordinator pane (re-gridding over leftover panes orphans their surfaces as dead tabs), collapse after; wmux send targets the caller's own surface by default (use --surface); agent kill doesn't kill agents' child processes (sweep orphaned dev-server ports).
  • New skills/orchestrate/references/browser-driving.md — field recipes for agents driving the browser panel: bare eN refs, the single persistent eval scope (IIFE), snapshot parsing, React/Radix synthetic-event input recipes, the transient-UI polling caveat, and the port-9222 CDP collision warning.

tawman added 3 commits July 7, 2026 00:55
The wmux CLI was only reachable two ways, and neither reaches a non-interactive
shell: (1) $WMUX_CLI, injected into every spawned shell's env but never read by
bare `wmux` callers; (2) an interactive-only `wmux` shell function dot-sourced
into the pane. So agent contexts that run bare `wmux` — Claude Code's Bash tool,
orchestrator hook scripts, the coordinator — had $WMUX_CLI but no `wmux` on PATH,
and had to rely on a hand-installed ~/.local/bin shim.

Prepend a bundled cli-bin/ (wmux + wmux.cmd, each `node $WMUX_CLI`) to PATH in
the env PtyManager builds for every shell. PATH inherits down the whole process
tree regardless of shell/login/interactive state — which is precisely what the
interactive function cannot do — so bare `wmux` now resolves everywhere with no
shim and no user PATH edit. The dir has no wmux.exe, so there is no PATHEXT
collision with the GUI; the interactive `wmux` function still shadows the shim in
the pane (function > PATH), so pane behavior is unchanged.

- src/main/pty-manager.ts: getCliBinPath() + case-insensitive PATH prepend.
- src/cli-bin/{wmux,wmux.cmd}: the shims (bash + cmd).
- .gitattributes: force LF on the extensionless bash shim.

Verified: with cli-bin prepended and ~/.local/bin removed, a non-interactive
`bash -c` resolves `wmux` to the cli-bin shim and `wmux ping` returns pong via
$WMUX_CLI. build:main + vite build clean, 158/158 tests.
The orchestrator calls bare wmux from non-interactive shells. On a patched wmux
the cli-bin shim is on PATH; on an un-patched/upstream wmux it isn't. Add
scripts/wmux-resolve.sh (defines a wmux() -> node $WMUX_CLI fallback), sourced
from orchestration-state.sh (covers all callers that use it) and directly in the
standalone detect-wmux.sh. Defining the function makes existing 'command -v wmux'
guards pass, so no other call sites change. App-bundled mirror; the standalone
tawman/wmux-orchestrator repo gets the same change.
getCliBinPath() resolves to process.resourcesPath/cli-bin in packaged builds —
add the src/cli-bin shims to extraResources so the dir actually exists there.
Byte-for-byte mirror of the wmux-orchestrator skill improvements into the
app-bundled plugin copy: result-file completion (interactive agents never
exit), coordinator-owned state.json with the sidebar's exited/complete
vocabulary, bare agent ids, Windows TMPDIR/state.json path rules, pane
hygiene, send --surface, plus the new browser-driving reference.
@amirlehmam

Copy link
Copy Markdown
Owner

Merged and released in v0.17.0 — thank you, this closes a real out-of-the-box gap for exactly the workflow wmux is built for.

The PATH-inheritance approach is the right call: it reaches bash -c children where the interactive shell function never could, and keeping wmux.exe out of cli-bin/ neatly sidesteps the PATHEXT trap you documented. Verified in the released artifact: resources/cli-bin/wmux + wmux.cmd ship in the zip (the extraResources entry carried over to CI packaging), and the case-insensitive Path key handling is in the compiled main. The wmux-resolve.sh fallback also means plugin users on older wmux builds get working orchestration immediately — nice defense in depth.

Also appreciated the folded-in orchestration lessons in the skill docs (state.json ownership, result-file completion signal, TMPDIR alignment on Git Bash) — those match battle scars from our own runs. All 162 tests green after merge.

tawman added a commit to tawman/wmux that referenced this pull request Jul 7, 2026
Syncs the fork with upstream master (release v0.17.0) — the first sync
carrying third-party code, security-gate reviewed before merge (PASS):

- amirlehmam#73 (Rajveerx11) pipe auth — security-positive: V1 commands now require
  an "auth <token>" prefix (ping stays public) and hook.event/agent.activity
  leave the tokenless V2 allowlist; timing-safe compare reused, clients
  (CLI sendV1, PowerShell integration) updated coherently, +4 auth tests.
  Closes the unauthenticated-local-pipe-write gap our own review flagged.
- amirlehmam#74 + maintainer CI commits — SignPath signing wired behind secrets (only
  egress is app.signpath.io with the repo's own secrets; inert on the fork),
  publisherName pinned via win.signtoolOptions, README updater-security docs,
  asarUnpack narrowed to node-pty prebuilds.
- Our merged amirlehmam#75 content (cli-bin OOTB, orchestrator resolver, skills
  lessons) arrives as no-ops; electron-builder cli-bin extraResources lands.
- No dependency changes (lock diff = version fields only).

Base bump 0.16.0 -> 0.17.0; fork version resets to 0.17.0-local.1.
Conflicts resolved: version files; .gitattributes (fork superset);
README.md + orchestration-state.sh (fork copies kept — hardening superset).
Fork CI repo-guard on release.yml survived the auto-merge (verified).

Verified: build:main + vite build clean, 169/169 tests, 0 vulns (full +
--omit=dev), security-touched files byte-identical to upstream.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants