* Fix Claude wrapper hook injection when cmux socket is stale
* Harden socket listener lifecycle and rearm policy
* Unset CLAUDECODE in stale-socket passthrough
* Harden listener cleanup and bound claude ping probe
* Guard socket unlink during listener startup window
* Add markdown viewer panel with live file watching
Introduce a new PanelType.markdown that renders .md files in a dedicated
panel using MarkdownUI (SwiftUI), with live file watching via DispatchSource
so content auto-updates when the file changes on disk.
- New MarkdownPanel class with file system watcher (write/delete/rename/extend)
- New MarkdownPanelView with custom cmux theme (headings, code blocks, tables,
blockquotes, inline code, lists, horizontal rules, light/dark mode)
- Full workspace integration: SurfaceKind, creation methods, tab subscription
- Session persistence: snapshot/restore across app restarts
- V2 socket command: markdown.open (validates path, resolves workspace, splits)
- CLI command: cmux markdown open <path> with routing flags and help text
- Agent skill: skills/cmux-markdown/ with SKILL.md, openai.yaml, and references
- Cross-link from skills/cmux/SKILL.md to the new markdown skill
- SPM dependency: gonzalezreal/swift-markdown-ui 2.4.1
* Fix unreachable guard in markdown subcommand dispatch
Use looksLikePath() to distinguish subcommands from path arguments
so the guard can catch unknown subcommands and future subcommands
are parsed correctly.
* Use .isoLatin1 fallback instead of .ascii for encoding recovery
ASCII is a strict subset of UTF-8, so falling back to .ascii after
UTF-8 fails is dead code. Use .isoLatin1 which accepts all 256 byte
values and covers legacy encodings like Windows-1252.
* Mark fileWatchSource as nonisolated(unsafe) for deinit safety
deinit is not guaranteed to run on the main actor, so accessing
@MainActor-isolated storage is a data race under strict concurrency.
DispatchSource.cancel() is thread-safe, so nonisolated(unsafe) is
sufficient with a documented invariant that writes only occur on main.
* Fix file watcher reattach: retry loop with cancellation guard
- Replace one-shot 500ms retry with up to 6 attempts (3s total window)
so files that reappear after a slow atomic replace are picked up
- Add isClosed flag checked before each retry to prevent restarting
the watcher after close()/deinit
* Harden path validation in markdown.open command
Reject directories and non-absolute paths before panel creation
to prevent ambiguous behavior and generic downstream failures.
* Always reattach file watcher on delete/rename events
After an atomic save (delete old + create new), the DispatchSource still
points to the old inode. Previously we only reattached when the file was
unreadable, so successful atomic saves left the watcher on a stale inode
and live updates silently stopped. Now we always stop and reattach:
immediately if the new file is readable, via retry loop if not.
* Restore markdown panels even when file is missing at launch
MarkdownPanel already handles unavailable files gracefully (shows
'file unavailable' UI and retries via the reattach loop). Dropping
the panel on restore lost the user's layout for files that may
reappear shortly after (network drives, build artifacts, etc.).
* Harden markdown CLI parsing and startup reconnect behavior
---------
Co-authored-by: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com>
Swift's split(separator:) omits empty subsequences by default, so a
payload like "||" or "||body" produces an empty or misaligned array.
Accessing parts[0] unconditionally then triggers an out-of-bounds trap
(EXC_BREAKPOINT / SIGTRAP).
Two changes:
1. Pass omittingEmptySubsequences: false to preserve field positions
across the pipe delimiters, so "title||body" correctly yields
["title", "", "body"] instead of ["title", "body"].
2. Guard parts[0] with a bounds check, consistent with how parts[1]
and parts[2] are already accessed.
Reproduces when cmux notify is called with empty --title or via
Claude Code's Notification hook where env vars may be empty.
* Clear sidebar notification when user submits prompt in Claude Code
Add UserPromptSubmit hook to the Claude Code wrapper that calls
`cmux claude-hook prompt-submit`. This clears the workspace notification
and sets status back to "Running" when the user addresses Claude's question,
so the "waiting for input" preview in the sidebar goes away.
Also adds --tab support to clear_notifications socket command and
--workspace support to the clear-notifications CLI command for
per-workspace notification clearing.
Closes https://github.com/manaflow-ai/cmux/issues/799
* Address review feedback: stricter error handling
- clear-notifications CLI: error on explicit --workspace failure instead of
falling back to global clear. Env var still gracefully degrades.
- prompt-submit hook: propagate sendV1Command errors instead of swallowing
with try?.
- clear_notifications socket: validate --tab flag is present before resolving,
reject malformed args instead of falling back to selected tab.
* Gate env workspace fallback on windowId == nil in clear-notifications
Matches the pattern used by other CLI commands to avoid using
CMUX_WORKSPACE_ID from the caller shell when --window targets
a different window.
* Add `cmux <path>` to open directories and Homebrew binary stanza
CLI: `cmux .` or `cmux /path/to/dir` opens a new workspace at the
given directory. If the app isn't running, it launches first and waits
for the socket. Also adds `--cwd` flag to `new-workspace`.
Server: `workspace.create` now accepts an optional `cwd` parameter,
passed through to `TabManager.addWorkspace(workingDirectory:)`.
Homebrew: adds `binary` stanza to the cask so `cmux` CLI is globally
available after `brew install --cask cmux`. Updated both the cask file,
the CI workflow template, and the manual release script so automated
version bumps preserve the stanza.
* Address review: validate cwd type, fix socket detection, propagate errors
- looksLikePath now also matches paths containing `/` (e.g. `foo/bar`)
- openPath uses socket connection attempt instead of fileExists to detect
whether the app is running (Unix sockets may not appear on filesystem)
- launchApp/activateApp now throw instead of swallowing errors with try?
- Server validates that cwd param is a string, returns invalid_params error
if wrong type is passed
- Wrap eval scripts in async IIFE that detects and awaits thenables,
using callAsyncJavaScript when available (macOS 11+) (#603)
- Register console/error telemetry hooks as WKUserScript at document
start so they survive navigation and are active before page JS (#604)
- Return typed envelope {__cmux_t, __cmux_v} from eval to distinguish
undefined from no return value; CLI prints "undefined" (#605)
- Keep dialog hooks as lazy injection only (not document-start) to
avoid suppressing WKUIDelegate native dialogs
- Add regression tests for async wrapper and undefined CLI rendering
Adds `cmux tree` that prints the window > workspace > pane > surface
hierarchy with box-drawing characters. Includes server-side system.tree
RPC for single-round-trip performance.
Features:
- --all flag for all windows (default: current window only)
- --workspace flag to filter to a single workspace
- --json for structured JSON output
- Active path markers (◀ active) and caller identification (◀ here)
- Browser surfaces show their current URL
Closes https://github.com/manaflow-ai/cmux/issues/586
Add a cached lazy keychain fallback to SocketControlPasswordStore so
that authentication paths in TerminalController can transparently read
a legacy keychain password without blocking on every request. The
keychain is read at most once and the result is cached behind an
NSLock. File-based and environment passwords still take priority.
Closes https://github.com/manaflow-ai/cmux/issues/579
* Fix manual unread clear race on focused tab
* Add mark-as-read tab action and show ring for manual unread
* Flash then clear manual unread on tab focus
* Add tmux rename-window workspace compatibility
Implement workspace.rename in the v2 API and wire CLI commands rename-workspace/rename-window with help text.
Add a regression test that validates API and CLI rename parity plus error handling.
Refs: https://github.com/manaflow-ai/cmux/issues/153
* Add full tmux compatibility command matrix and regression coverage
Addresses review feedback from https://github.com/manaflow-ai/cmux/pull/219 by resolving read-screen targets against requested workspace/surface instead of the selected workspace.