Skip to main content
CHANGELOG

What shipped

Released versions only, generated at build time from the product Keep a Changelog file. Unreleased work is not listed here: if it is not in a version heading, it has not shipped.

Latest released v3.23.4 · · 61 releases

Currently downloadable v3.25.2-preview.1 · preview channel

Those are two different questions. This page lists versions that have been released, from the product changelog. The download page serves whatever the live preview channel points at, which moves ahead of the last released version with every build. Download the current build from /download. Channel and exact version always come from the live release manifest.

Releases 36–40 of 61

Page 8 of 13

v2.17.1

MCP pane-lifecycle fixes

Two small MCP fixes on top of v2.17.0, both dogfood-verified on a live build before tagging.

Fixed

  • browser_close no longer leaves an empty pane behind. The UI close path removes a pane when its last surface is closed, but the MCP mirror only closed the surface — leaving an empty leaf that the "auto-create initial surface" effect backfilled with a fresh terminal. A browser_open/browser_close loop accreted blank PowerShell panes. The handler now snapshots whether the closed surface was the pane's last one before removing it, then cascades into closePane to mirror the UI path. A browser sharing a split pane with a terminal still only loses the surface; a browser that is a workspace's only (root) pane still gets an auto-terminal, matching the UI exactly. (#144)
  • Stale pid-map anchors are pruned on workspace/pane close. The pid→ptyId anchor that backs MCP workspace-identity resolution was only pruned on session:died, so closing a workspace or pane through the UI (the destroySession path) leaked its anchor. Over time those stale entries could mis-resolve a ghost workspace identity. Closing now prunes the anchor immediately, in lockstep with the session teardown. (#142)

v2.17.0

security hardening sweep, packaged browser fixes, workspace UX

The big batch since v2.16.2. Headline: a security-hardening sweep across the daemon, MCP, A2A, release pipeline, and browser surfaces — most of it surfaced by an external codex security scan, with each finding triaged and adversarially verified before merge (a chunk turned out to be false-positives or duplicates and were closed rather than merged). Plus a set of fixes that make the embedded browser tools work on packaged builds, per-workspace environment/startup profiles, and the workspace-management UX that profiles implied (duplicate, per-terminal working directories). No config changes required — defaults are unchanged.

Added

  • Per-workspace process profiles. Each workspace can define environment variables and an optional startup command, applied to new panes only — existing and recovered daemon PTYs keep their create-time environment. Right-click a workspace → "Configure profile…". Generic by design (no provider hardcoding): point CLAUDE_CONFIG_DIR, CODEX_HOME, SSH wrappers, etc. at different accounts per workspace. This is environment separation, not an OS-level security sandbox. See docs/workspace-profiles.md for setup and multi-account recipes. (#101, #103)
  • Workspace management actions. Right-click a workspace to duplicate it — the layout (fresh pane/surface ids, cleared ptyIds so new panes spawn their own PTYs) and the profile (re-normalized through the secret-name policy) are cloned as <name> (copy N). A new Working directories menu lists each terminal's live cwd with copy; every terminal now tracks its own cwd (shown in the tab tooltip), terminal tabs can be renamed (double-click), and an accidental workspace close is guarded by a confirmation. New-pane semantics throughout, consistent with the profile contract. (#141)

Security

  • Token-file ACL grants by the labeled SID. getCurrentUserSid parsed the first SID-shaped substring of whoami output, so a SID-shaped account or machine name (e.g. S-1-1-0 = Everyone) could be granted the auth-token ACL instead of the real owner, leaving the token world-readable. It now parses the explicit SID: field. (#118)
  • Token-file DACL is rebuilt owner-only, even on the upgrade path. The shipped icacls /grant:r … /inheritance:r only replaced the named principal's ACE and stripped inherited ACEs, so a pre-existing explicit broad ACE (e.g. Everyone:(R) from a redirected/roamed/MDM profile) survived and left the token world-readable. The DACL is now rebuilt with a .NET DACL-only primitive (no owner/group/SACL writes, so it needs no privilege and succeeds on the upgrade-from-icacls state), with icacls as a fail-closed fallback when PowerShell is blocked. (#140)
  • MCP approvals bind to the reviewed capability snapshot. A plugin could redeclare broader capabilities while an approval prompt was pending and get trusted for a set the user never saw (a TOCTOU between consent and call). The approval now pins the exact capabilities shown in the dialog. (#122)
  • Terminal drops are restricted to wmux drag sources. Text dragged from a browser or another app no longer routes straight into a terminal PTY (where embedded newlines could auto-run at a shell prompt); only internal wmux drags — sidebar, surface tabs, file tree — write to the pane. (#123)
  • Default MCP terminal resolution fails closed. A spoofable WMUX_WORKSPACE_ID env hint or a failed workspace claim could fall back to the user's active pane, i.e. cross-workspace keystroke injection/read. Terminal tools now require a verified, PID-mapped identity and refuse the env hint, throwing rather than touching the focused pane. (#125)
  • A2A sender identity is authoritative. Company A2A send/broadcast no longer accept a caller-supplied from; the sender is derived from the authenticated workspace, so one agent can't impersonate another (or the CEO) when delivering a message into a peer's terminal. (#129)
  • Inter-agent PTY delivery is bracketed-paste wrapped. A2A messages written into a peer's terminal are bracketed and ESC-sanitized so an embedded newline can't submit a command in the receiving shell. (#132)
  • Remote SOUL prompt loading is disabled. Company agent personas were fetched from a third-party URL at spawn and written verbatim into the agent's instruction file — a remote prompt-injection / supply-chain path into command-capable agents. Spawning now uses the built-in role prompts only. (#131)
  • RPC browser profile switching is scoped. An RPC caller could mount an arbitrary Electron persistent partition or the human's pre-seeded login session store (reading its cookies over CDP). Profile names are now validated and RPC selection is restricted to a safe allowlist. (#133)
  • IPv6 navigation SSRF hardening. The navigation URL validator now un-brackets and bit-masks IPv6 literals (unique-local, link-local, IPv4-mapped), closing a bracket bypass that reached internal addresses through the browser_tabs new-tab path. (#137)
  • Bundled first-party MCP server runs under enforce mode. Under packaged enforce mode the bundled server was denied because it never went through declare/approve, so wmux's own tools were locked out. A name-recognized, scoped allowlist lets the first-party tools run without opening the gate to third-party servers. (#109)
  • Release pipeline hardening. The release tag is passed through env: instead of being interpolated into a shell run: block (Actions script-injection); the SignPath token is scoped to the signing step instead of the whole job; third-party release actions are pinned to immutable commit SHAs; WinGet publishing moved to a least-privilege job; and the installer fails closed when the checksum manifest is missing or invalid. (#119, #120, #121, #126, #135)
  • Recursive IPC error-log redaction. The structured IPC error logger now redacts sensitive keys at any depth, redacts startup-command values, and summarizes env maps to a key count — so workspace-profile env/commands flowing through pty:create can never leak into args_summary. Profile env is also kept out of the copy-session-info / drag-export markdown, and reserved WMUX_* keys are rejected so a profile can't spoof workspace identity. (#103)
  • Child shells never inherit a stale wmux identity. A wmux launched from inside a wmux pane (e.g. npm start while dogfooding) inherited the parent pane's WMUX_WORKSPACE_ID / WMUX_SURFACE_ID / WMUX_SOCKET_PATH in its own environment, which could survive into freshly created child shells. The whole reserved WMUX_* namespace is now cleared from the spawn baseline before identity is forced, so a child's identity is only ever what wmux explicitly sets — the spoofing guarantee is now unconditional, not profile-only. (#141)

Fixed

  • Embedded browser tools work on packaged builds. On packaged builds getPage() can't surface the <webview> guest as a Playwright Page, so a swath of browser tools failed with No browser page available. They now fall back to the main-process CDP/RPC channel: DOM tools read the real webview instead of the wmux app shell (#104), extraction/snapshot (#105), console/network/response-body capture (#106), browser_extract_data field mapping (#110), cookies/storage/emulate/resize (#111), geolocation grants + reset semantics (#112), and browser_wait (#114). browser_open/session_start route through requireWorkspaceId so the browser opens in the calling workspace, not the active one (#96).
  • Memory-leak audit survivors. Three real leaks found in a leak audit are now bounded: the MCP capture buffer (a Page-keyed WeakMap), the A2A GC hard cap, and PTY listener cleanup. (#102)
  • Per-terminal working directory is reported correctly (local and daemon mode). The tab tooltip and the workspace "Working directories" menu showed each shell's startup home directory (e.g. C:\Users\me) for every PowerShell regardless of where it had cd'd. Two compounding parser bugs are fixed: OSC 7 left Windows paths as /C:/Users/me (leading slash, forward slashes), and prompt detection matched the stale echoed prompt and froze the reported cwd at startup. Parsing is extracted into a unit-tested cwdDetect module (shared by both spawn paths) that normalizes the OSC 7 URI to a native path — including UNC shares — and reads the live (last) prompt. Daemon mode additionally never forwarded its detected cwd to the renderer; a new session:cwd event now closes that gap so daemon-backed panes live-update like local ones. (#141)
  • Tighter workspace right-click menu. The context menu was pinned to a fixed minimum width, leaving a wide blank gutter beside short items (and an oversized gap before the "Working directories" submenu arrow); it now sizes to its content. (#141)

Contributors

This release leaned on the community — two external contributors landed real features and fixes, not just reports.

@junbeom09 (조준범) carried forward the packaged-build hardening he started in 2.16.2. Dogfooding the packaged app, he found the browser DOM tools were silently reading the wmux app shell instead of the embedded <webview> — a bug that never reproduces in a dev build — and contributed the runtime shell-detection fix (#104). He then verified the CDP capture and geolocation fallbacks (#108/#112) on a real install, confirming the exact paths CI can't prove. Fixes and reports from real-world setups a single maintainer never sees are how wmux gets more robust.

@snowyukitty had the busiest release of anyone. He built per-workspace process profiles end to end (#101), then followed up after review with path-pointer credential-var allowlisting and non-destructive profile loading so an existing profile is never clobbered on load (#103). He shipped the workspace-management UX that profiles implied — duplicate workspace, the working-directories menu, per-terminal cwd tracking, tab rename, and close confirmation — and fixed the OSC 7 / prompt-detection cwd bugs and the child-shell identity-inheritance leak along the way (#141). He also split the Vitest runtime lane (#97) so timing-sensitive tests run serially instead of flaking under parallel load.

The security-hardening sweep (#118–#137) was surfaced by an external codex security scan; each of the 20-plus findings was triaged and adversarially verified before landing, with false-positives and duplicates closed rather than merged. The token-file ACL rebuild (#118 plus the DACL-only primitive in #140) was additionally dogfooded against a real %USERPROFILE%\.wmux descriptor — a directory that grants SYSTEM and Administrators inherited FullControl — to confirm the hardened token comes out owner-only with no self-lockout.

Maintained by @openwong2kim, with engineering and code-review pairing by Claude (Anthropic). Thanks to everyone filing issues and dogfooding. 🙏

v2.16.2

daemon hardening: security, split-brain fix, configurable lifecycle

Bundles everything merged since v2.16.1: a token-file permission hardening (security), the duplicate-daemon / split-brain fix behind the "relaunch resets my terminals" bug, configurable daemon lifecycle thresholds, and idle-reap diagnostics. No config changes are required — defaults are unchanged.

Security

  • Token-file ACL is applied by owner SID, not username. The daemon auth-token file's ACL was tightened by passing the account name to icacls, which mojibakes under the OEM codepage for non-ASCII (e.g. Korean) usernames and could lock the owner out of their own token. The ACL is now keyed by the owner's SID, with an ASCII-only fallback guard. (#90)

Fixed

  • No more duplicate daemon / split-brain on relaunch. "Quit (keep sessions) → relaunch" could spawn a second daemon that fell back to a -N-suffixed pipe, leaving the first daemon's session pipe in EADDRINUSE and the UI unable to reattach — terminals appeared to reset. A three-defect chain is closed: isProcessAlive swallowing its probe error into false, the canonical-pipe reclaim conflating a live owner with a zombie, and the -N fallback itself. A confirmed live owner on the canonical pipe now makes the redundant daemon exit cleanly so the launcher reconnects to the existing one. (#93)
  • maxSessions counts only live sessions. Dead tombstones no longer occupy slots against the cap, so a low maxSessions won't be exhausted by sessions that have already exited. (#92)
  • Recovered sessions keep their saved dead-TTL. A recovered session preserves the dead-session TTL it was created with instead of silently inheriting the current default. (#92)

Added

  • Configurable lifecycle thresholds. Five daemon limits became config keys with the former hardcoded values as defaults: maxSessions (200), the memory warn/reap/block triple (500/750/1024 MB), and suspendedTtlHours (7d). Out-of-range or malformed values are clamped per-field — not whole-file reset — with a startup warning, so a single bad value can't brick the daemon. maxRecoverSessions is derived from maxSessions rather than configured separately. Documented in PROTOCOL.md §7–§8. (#92)
  • Idle-shutdown diagnostics. When the daemon is held alive past its grace window, the watchdog now logs which signal is keeping it up (active connections vs. live sessions) or that it is counting down to self-terminate, so a daemon that fails to reap an empty session set can be diagnosed from its log instead of a live-process inspection. (#95)

Contributors

Special thanks to @junbeom09 (조준범) for the token-file ACL hardening (#90). He hit the non-ASCII-username lockout firsthand: a Korean account name turned the icacls principal into mojibake under the Windows OEM codepage and locked the owner out of their own auth token. He traced the root cause and contributed the SID-based fix that makes the hardening codepage-proof for every user. Reports like this, from real-world setups a single maintainer never sees, are exactly how wmux gets more robust. 🙏

Maintained by @openwong2kim, with engineering and code-review pairing by Claude (Anthropic). Thanks as always to everyone filing issues and dogfooding the daemon-lifecycle work.

v2.16.1

daemon false-death fix, resize console-spam fix

A stability patch. The headline: on slow or loaded machines the daemon's process monitor could mistake a probe timeout for a dead process and reap a session that was actually alive, so sessions appeared to close on their own. That's fixed. It also quiets an Uncaught (in promise) console flood on relaunch and adds session-death logging so future "why did my session close" reports are diagnosable.

Fixed

  • Live sessions are no longer killed on a probe timeout. ProcessMonitor treated a slow or timed-out tasklist probe as proof the process had died and reaped the still-alive session — the cause behind sessions closing by themselves under CPU contention or a Defender scan. It now reaps only on positive confirmation of death; a probe that fails or times out defers instead of killing.
  • No more Uncaught (in promise) flood on relaunch. A burst of terminal resizes during reconnect could exceed the daemon's per-socket rate limit, and the renderer never caught the rejection, spamming the console. Resize calls now swallow the transient rejection and re-send the live geometry once after the rate window clears, so a resize dropped during the burst self-heals instead of leaving the terminal stuck at the wrong size.

Added

  • PTY session-death logging. When a session dies the daemon now logs its exit code, signal, and idle time, so an unexpected session close can be diagnosed from the log instead of guessed at.

v2.16.0

tmux-style persistence, blank-relaunch fix, multiline-paste fix, stability batch

Bundles everything merged since v2.15.0 (#81, #84). The headline is tmux-style persistence — closing the window now keeps your daemon and sessions alive and reattaches them on next launch — plus the fix for recovered sessions rendering blank on relaunch, a multiline-paste fix for PowerShell, and a batch of dogfood-driven stability and UX changes.

Added

  • Quit keeps your sessions running. The tray now offers "Quit (keep sessions running)" — it detaches the UI while the daemon and all PTYs survive, and the next launch reattaches them — plus a separate "Shut down wmux (close all sessions)" for a full teardown. This is the tmux model the README always described.
  • Ctrl+Shift+Arrow moves focus between panes (and between grid tiles in multiview) in all four directions. Bare Ctrl+Arrow is intentionally unbound.
  • Completion blink. A pane whose agent just finished (or is waiting / awaiting input) blinks its border, and its background tab shows a status dot, so you can see which terminal needs you without hunting. Clears on focus; respects prefers-reduced-motion.

Changed

  • Quit now detaches instead of killing the daemon. before-quit previously tried to shut the daemon down on every quit (the opposite of tmux), and a hung handler could orphan it. The default quit now only detaches; full shutdown is explicit and guaranteed to exit.
  • RAM readout is real RSS (app.getAppMetrics working-set sum) instead of the renderer's JS heap, so the StatusBar number reflects actual process memory.
  • Removed the token-usage chip. The regex-scraped per-pane token estimate was unreliable and is gone, along with its IPC and tracker. The measured 5h / 7d usage-percentage widget stays.
  • Right-click copy keeps the selection and no longer collides with the paste gesture (a fast second right-click used to paste over a just-copied selection).
  • Multiline paste into PowerShell inserts a clean multiline command instead of injecting whitespace at every line break (see Fixed).

Fixed

  • Recovered sessions no longer render blank on relaunch (#81). Daemon reattach ran inside the terminal-creation effect behind an isCurrent guard evaluated before the effect assigned the terminal ref, so pty.reconnect never fired (live daemon sessions, zero attach). Reattach moved to a dedicated effect that runs after the ref is set and also fires on daemon:connected (late-connect / respawn).
  • Orphan daemon on quit. A hung before-quit pipe-close could leave wmux.exe running after the window closed; full shutdown now force-exits within a bounded timer.
  • Multiline paste injected whitespace in PowerShell (#84). normalizePasteText collapsed every newline to a lone CR, but inside a bracketed-paste body PSReadLine treats CR as Enter and misplaces the cursor (PSReadLine #3939, #417, which both recommend LF). It now emits LF as the in-body separator when bracketed (CR otherwise), fixing all four paste paths (Ctrl+V, Ctrl+Shift+V, right-click, Shift+Insert / onData). Verified against real pwsh 7.6 / PSReadLine 2.4.5.
  • prefix + arrow keys. Session load now merges the saved prefix config over the defaults instead of replacing it wholesale, so arrow-key pane-focus bindings survive a reload.
  • WebGL context thrash. An LRU pool (max 12) caps live WebGL terminal contexts, preventing the "too many contexts" eviction that could blank panes when 16+ are visible at once.

Security

  • Paste-injection guard (#84). The bracketed-paste body sanitizes a raw ESC to U+241B, so pasted text can no longer forge the ESC[201~ close marker and run trailing bytes as a command.

Docs

  • Dropped the removed Ctrl+Up/Down scroll-bookmark jump shortcut from the README. Ctrl+M marking and the gutter indicators still work.