Repository navigation
Description
Summary
wmux agent spawn --pane <id> without --workspace records the agent under whatever workspace is currently active, never checking that the pane actually belongs to it. The agent starts and runs, but its surface is then unreachable through read-screen / current-workspace --surface, so anything driving agents over the CLI loses the ability to see what they are doing.
Found while orchestrating a multi-agent run: panes were created in the right workspace, the agents were tagged with a different one, and three waves were restarted chasing what looked like a cwd fault.
Where
src/main/index.ts, the agent.spawn pipe handler (~:1726):
let workspaceId = params.workspaceId;
if (!workspaceId) {
const wins = BrowserWindow.getAllWindows();
if (wins.length > 0) {
workspaceId = await wins[0].webContents.executeJavaScript('window.__wmux_getActiveWorkspaceId?.()');
}
}
if (!workspaceId) { respondError(-32000, 'No active workspace'); return; }
let paneId = params.paneId; // <-- taken verbatim
if (!paneId) { /* distributeAgents fallback */ }
...
agentManager.spawn({ cmd, label, cwd, env, paneId, workspaceId });The two are resolved independently. When paneId is supplied explicitly, nothing derives its owning workspace and nothing rejects a contradiction, so agentManager.spawn gets a (paneId, workspaceId) pair that does not exist in the split tree.
agent.spawn_batch immediately below resolves workspaceId the same way, so it looks like it has the same gap.
Repro
wmux 2.12.0, Windows 11 26200. Active workspace is "Personal-1"; the pane is created in "Admin".
# 1. pane in a NON-active workspace — layout grid honours --workspace correctly
wmux layout grid --count 2 --type terminal --workspace <ws-ADMIN>
# -> newPaneIds[0] = pane-c386d74f-… (confirmed present in Admin's list-panes)
# 2. spawn into that pane, no --workspace
wmux agent spawn --cmd "echo repro" --label repro --pane pane-c386d74f-…
# 3. what wmux recorded
wmux agent list
# REPORTED workspaceId: ws-1fb8c544-… (Personal-1, the ACTIVE one)
# paneId: pane-c386d74f-… (the Admin pane)Expected: the agent is recorded under the workspace that owns the pane, or the call is rejected as contradictory.
Actual: the record pairs the Admin pane with the Personal-1 workspace.
Consequence
The surface becomes unaddressable:
wmux read-screen --surface surf-4e85e81f-…
Error: no terminal for surface surf-4e85e81f-… (markdown/browser pane, another window, or closed)The agent is running fine — agent list shows it with a live PID, and focus-surface accepts it. Only the lookups that go through the workspace record fail, and the error message points at three causes none of which is the real one. For a CLI-driven orchestrator this is the difference between watching agents and flying blind; it also makes the failure look like a dead or misconfigured agent rather than a bookkeeping mismatch.
Suggested fix
When params.paneId is supplied, derive workspaceId from the pane rather than from the active workspace — the renderer already owns the split tree, so this is the same kind of lookup browser.* does with engineForSurface. If both are supplied and disagree, -32602 is more useful than a silently mismatched record.
Worth noting --workspace already exists on agent spawn and passing it explicitly avoids the problem — but the failure is silent when it is omitted, and --pane alone reads as fully specified.
Not a bug (checked while narrowing this down)
--cwdis parsed and forwarded correctly (src/cli/wmux.ts:504→agent-manager.ts:79). I initially suspected it and could not reproduce any failure.wmux layout grid --workspacehonours the flag; the pane lands where asked.
Activity
fix(agent): derive a spawn's workspace from the pane that owns it (#242)
chore(release): bump to 2.13.0, and CLAUDE.md for #242/#243
amirlehmam commentedon Sep 18, 2026
Fixed in 2.13.0. Your diagnosis was exactly right, including the line you pointed at — thank you for narrowing it that far, and for ruling out
--cwdon the way.The fix. The pane is now authoritative whenever it is given. Resolution moved into a single module (
src/main/agent-spawn-target.ts) that decides the pane and the workspace together instead of independently:--panealone → the workspace is derived from the pane that owns it, never from the focused one.--panereads as fully specified because it now is.--pane+--workspacedisagreeing →-32602, naming both sides, rather than a record pairing a pane with a workspace that does not contain it.-32602, rather than what it did before: spawn the agent, broadcast anAGENT_UPDATEaddressing a pane that does not exist, and report success.One thing that fell out of it: with a pane named, the active workspace is never consulted at all — so a fully specified spawn now works when there is no active workspace. It used to fail
-32000for a reason unrelated to the request.You were right about
spawn_batch, and it was the mirror of what you found.__wmux_getPaneLoadsalways read the active workspace, so--workspace <other>was honoured for the record it wrote and ignored for the panes it picked: every agent landed in the active workspace's panes while being filed under the other one. Same mistake, opposite direction, so both now resolve through the one module. Related:wmux agent spawn-batchnever parsed--workspaceat all, so there was no way to tell main about one even after it learned to honour it — that flag exists now.Why it's shaped the way it is. The decision is pure with the renderer lookups injected, because the split tree lives in the Zustand store and main has no copy of it — every question is a round trip, and the part worth testing is the rule, not the asking. The impure half asks every window and takes the first real answer: a workspace is not a window (#143), and a miss here is not a benign fallback, it is telling a caller their live pane does not exist. 20 tests cover it, including your exact two-workspace repro.
The error message you quoted stays as it is for the genuine cases, but the path that made it lie is gone.
fix(cli): resync the checked-in CLI mirror after the spawn-batch flag (…
amirlehmam commentedon Sep 18, 2026
Shipped in v2.13.0 — zip and setup.exe both up. Thanks again for the precise write-up; the
spawn_batchhunch was right and turned out to be the mirror case.