Changelog
All notable changes to ccmux. The [Unreleased] section accumulates as work lands;
bun run release X.Y.Z "notes" rolls it into a dated version section, and CI publishes
the GitHub Release with that section as the notes.
[Unreleased]
[0.1.19] — 2026-07-25
session-reader library seam — expose the tested block-parser as ‘ccmux/session-reader’ for external consumers (readSession/parseSession/detect + types), lean (no ink/react), inert for the fleet bundle
- Expose the transcript reader as a library —
ccmux/session-reader(src/lib.ts+ a package.jsonexportssubpath).readSession(path, agent, textLimit?)/parseSession(lines, agent, textLimit?)/detect(lines)plus theTranscriptMessagetypes, so an external consumer reuses the tested, agent-agnostic (Claude + Codex) block-parser instead of duplicating it.textLimitis a passthrough (default 6000; pass higher for full-text indexing). Lean by construction: the seam wires only the PURE parsers +readLines+ types, so importing it pulls inzod+node:fs— not ink/react/tmux (verified:src/lib.tsbundles to ~15 KB with zero ink/react in the subgraph). Inert for the fleet: the shipped bundle is built fromcli.ts, andexportsonly affects externalimport "ccmux/…"resolution. The format sniff moved tosrc/agent/detect.ts(normalize-only deps) and is re-exported fromagent/index.tsunder its existing name.
[0.1.18] — 2026-07-24
fix: ‘ccmux update –check’ is now read-only and a stale/older staged bundle can’t silently downgrade a machine
- Fix
ccmux update --checkmutating the machine. With a leftover staged bundle present (a forgottenbun run stage),--checkapplied it instead of just reporting — and applied it even when it was OLDER than the running version, silently downgrading. Two root fixes: (1) the update decision is now a puredecideUpdatethat--checkcan only ever ask for aprintfrom — read-only by construction; (2) a staged bundle wins only when NEWER-or-equal than the running version (an unreadable one counts as not-newer) — a stale/older staged build is refused as a downgrade unless--force, with a message pointing at the forgotten file. The legit “test a newer build locally” path is unchanged.
[0.1.17] — 2026-07-24
chat-layer follow-ups from acceptance testing: cancellable watchdogs (msg cancel + –task dedup), honest single-source usage, stdin body, –after+–defer trap warning
ccmux msg cancel <task>— drop a sender’s still-undelivered mail for a task (an armed--afterwatchdog or a queued--deferthat hasn’t fired). Cancellation is a tombstone in the append-only ack-log (by: "cancel"), the same log the daemon and the Stop hook already consult — so a cancel suppresses delivery in BOTH channels with no new coordination surface and no ledger rewrite. Scoped to the sender, so a session can never cancel another’s dispatch; already-delivered mail can’t be un-sent.- Watchdog dedup by task: re-arming a conditional (
--defer/--after) with the same(sender, recipient, task)now REPLACES the sender’s prior undelivered one instead of piling up a duplicate ping — closing the router’s “two identical watchdogs both fired” gap. Immediate mail is never replaced (it’s delivered at once). --after+--defernow prints a note on send: the two multiply to “not before T AND only at a turn boundary”, so a self-watchdog armed that way won’t arrive on time in a long turn — a watchdog should use bare--after(delivered between tool calls). Not blocked; the flags are compatible.ccmux msgreads the body from stdin (echo "…" | ccmux msg <to>) when no inline text is given and stdin isn’t a TTY — matching the rest of the toolchain.ccmux msg --helpnow shows the full flag set (--defer/--after/--on-behalf-of/cancel). The short help and the arg-error usage were two separate strings that had drifted; they now render from one source, so they can’t diverge again.ccmux inboxhelp clarifies it’s the fallback for held/offline mail, not an archive — a message already pushed into a pane doesn’t sit there.- Diagnostic: the daemon now logs when it HOLDS a pending message solely because a human is attached to the recipient’s pane (delivery resumes on detach) — so “the message never arrived” is traceable to that transient cause instead of looking like a broken chat.
[0.1.16] — 2026-07-24
inter-agent deferred chat + autonomous router sessions + time-delayed watchdog delivery
- Deferred chat delivery:
ccmux msg <to> --deferholds a follow-up until the recipient VOLUNTARILY finishes its turn, then delivers it as if a human typed it — never interrupting mid-work (Claude’s native queue is steering; it flushes between tool calls). Delivered by a Claude Stop hook the instant the turn ends, or by the daemon once the target is stably idle (spinner off + assistant-message-last + transcript quiet for a grace window). The Stop hook is auto-provisioned at launch, merged into a single--settingsobject (verified it does not clobber the user’s own hooks). Coordination is an append-only ack-log keyed by message id — the daemon stays the sole writer of the delivery cursor, so there is no lost-update race and noblock-loop. - Router sessions — an autonomous manager.
ccmux new <name> <dir> --router/ccmux router on|off <name>gives a session a versioned “manager protocol”: it routes an owner-dictated follow-up to the right target with--defer, waits, validates the result against a stated done-criterion, re-asks on a gap (bounded), and escalates to the human ONLY when genuinely stuck — never nagging with “continue?”. Activated via apromptModulesdata field (a key into an in-code module registry, resolved fresh at every launch — no stale snapshot), so it’s a capability toggle, not a persisted role. - Time-delayed delivery:
ccmux msg <to> --after <sec>(anotBeforeinstant). A router arms a self-watchdogper dispatch, so a target that finishes but never reports back no longer hangs it — the timer returns control, the router checks the transcript and closes or escalates on its own. Delivery is now two-track — immediate mail flows in order through the cursor, while deferred / time-delayed mail is delivered by id off the cursor, so a pending conditional message never head-of-line-blocks an immediate reply behind it. - Honest relay provenance:
ccmux msg --on-behalf-of <who>renders “on behalf of” so a router can carry the owner’s authority without ever spoofing the sender — gated so only a router (or the cli) may relay, never a plain peer. - Owner-language: sessions reply to
ownerin the owner’s own language by default; an optionalownerLangin machine.json forces a fixed language.
[0.1.15] — 2026-07-19
inter-agent chat (menu-safe pane delivery + one-way Telegram mirror) + isolated dev instance
- Isolated dev instance: run a full second ccmux (daemon + sessions + chat) beside prod on one
machine, fully isolated — a
tmuxSocketconfig scopes every tmux call to its own server (-L),CCMUX_HOMEoverridable for its own app/log/boot-state,remoteControl:falsekeeps its sessions out of the claude.ai app. tmux doesn’t propagate env into panes, sonew-session -epins the instance’sCCMUX_HOME/CONFIG/SESSIONS, and the injected prompt teaches the instance’s own cli (not the prod shim) whenCCMUX_HOMEis non-default. Scaffold + teardown viascripts/dev-instance.sh. - Inter-agent chat, sender identity: the sender is automatic and unspoofable — an agent sends as its
own session, a command-line invocation as
cli; there is no--from.owneris a reserved recipient (the human — Telegram-only, no pane); the injected prompt frames[chat from owner|cli]as the human side (user-level trust) vs[chat from <peer>]as a fellow agent. - Dev daemon hot-reload:
bun daemon:watch(=bun --watch src/cli.ts daemon) restarts the process on any source change — fresh timers each time, unlike--hot, which re-runs the entry WITHOUT tearing down the oldensure/chat loops (they’d accumulate; proven with aBun.sleepprobe). The boot-loop guard is now skipped when running from live source (IS_DEV): it protects the auto-updated prod bundle (revert to.bak), has no bundle to revert in dev, and would only churn false “boot-loop” errors under rapid--watchrestarts. - Telegram chat mirror: the routing header (
from → to, or📩 for you — from …for a message to the human) is now bold (HTML parse_mode) so who-is-talking-to-whom reads at a glance; the message body is HTML-escaped so</>/&render verbatim and never trip a 400 that would drop the message. - Inter-agent chat: opt-in messaging between managed sessions.
ccmux msg <to> "..."/ccmux inbox/ccmux chat log|on|off, with a per-sessionchatEnabledflag (default off). The daemon push-delivers into the recipient’s pane on a fast cadence, tagged[chat from X](framed to the agent as a peer, not the user), gated so it never injects at a selection menu (would auto-pick an option — proven live) or while a human is attached; a busy recipient just gets it queued at its next turn boundary. In-order per recipient, no double-push across daemon bounces; loop/rate guards cap a runaway A→B→A. An append-only ledger (~/.ccmux-chat.jsonl) is the source of truth; multi-line bodies deliver via bracketed paste. Optional one-way Telegram mirror (telegramin machine.json → group/DM/topic; fail-soft, outbound only).
[0.1.14] — 2026-07-19
auto-answer Claude’s resume-from-summary picker so daemon-healed reboots don’t strand large sessions at the menu
- Auto-answer Claude’s blocking “Resume from summary?” picker on an unattended resume. Claude
2.1.x shows this menu on
--resumeof a large/old session; a daemon-healed reboot had nobody to answer it, so big sessions stranded at the menu (typed input — app or tmux — landed on the menu, not the conversation) until a human manually restarted each one. The_runsupervisor now watches the freshly-resumed pane and answers per a newresumePickermachine-config policy (full= keep all context [default] ·summary= compact ·off= leave for a human). It reads the option NUMBER from the pane (robust to reordering) and confirms with Enter only if the number key didn’t. Claude-only; other agents have no such picker.
[0.1.13] — 2026-07-17
injected prompt teaches bare ccmux shim, not the absolute bun path
- The sibling-management prompt injected into each session now teaches the bare
ccmuxcommand (the PATH shim) instead of an absolutebun …/ccmux.jspath, so fleet agents call it cleanly. Falls back to the absolute invocation only when the shim isn’t installed. The machine re-execs (supervisor, boot unit, restart-worker) stay absolute.
[0.1.12] — 2026-07-17
per-session permission-mode override
- Sessions get an optional
permissionModethat overrides the machine-wide default (MachineConfig.permissionMode). Undefined → inherit the machine default, so existing sessions and configs are unchanged. Lets one box run bypass by default while a specific session (client-prod, untrusted-input) stays gated atauto/plan. - New
ccmux mode <name> <mode|default>sets/clears the override (default= inherit the machine default). It’s a launch-time flag →ccmux restart <name>applies it. - The root-guard is unchanged and still applies to the resolved mode: under a root daemon,
escalated modes (
bypassPermissions/dontAsk) still downgrade toauto, whether they came from the machine or the session.
[0.1.11] — 2026-07-16
transcript whole-session composition stats
- transcript: whole-session
stats { messages, user, assistant, toolCalls, thinking }on every--jsonresponse, counted over the ENTIRE JSONL (not just the loaded window) and cached by mtime — idle sessions cost nothing, active ones recompute only when they move. Lets a viewer show true session composition that doesn’t drift as you paginate.
[0.1.10] — 2026-07-16
transcript backward pagination — infinite-scroll-up
- transcript: backward pagination for infinite-scroll-up.
transcript --json --before <line> --limit <n>returns thenlines ending just before<line>(line-based, so it’s robust to lines that carry no message — blank / folded tool_result), and every response now carrieswindow { firstLine, lastLine, reachedStart }so a consumer can page older until line 1.parsegained an optionalendLineupper bound (claude + codex parsers).
[0.1.9] — 2026-07-16
transcript: full tool input + result output for the expanded card
- transcript:
TranscriptMessagegainsinput(the tool_use input as pretty JSON — the actual command/args) andresultText(the paired tool_result’s full output), both clipped to the display text limit. Consumers can now render a real request→response body per tool call instead of only the one-line summary. Claude + Codex parsers emit both; null for non-tool messages and still-running calls.
[0.1.8] — 2026-07-14
release pipeline v2 — CI-only publishing
- Release pipeline v2: releases are born only from tags via CI (gate: typecheck + tests +
bundle smoke); local
--publishremoved;bun run release X.Y.Z "notes"is the one ceremony (clean-tree guard → check → bump + changelog → commit → tag → push). - Pre-push git hook runs
bun run check(wired viacore.hooksPath).
[0.1.7] — 2026-07-14
- Follow the fork: the registry re-pins a session to the new session id when Claude Code forks the conversation (out-of-context continuation) — previews, transcripts, activity and the next restart follow the live conversation instead of a dead file.
- External discovery ignores processes living inside managed panes (a fork leaves the
pane’s stale
--resumeargv looking like a live external session showing a dead conversation). - TUI: fleet sorts by last conversation activity (minute-bucketed, no per-tick reshuffle);
cards show the activity age (
5m ago) next to uptime; selection follows the session (uuid), not the list position.
[0.1.6] — 2026-07-11
- Configurable permission mode (all Claude Code modes) in machine.json; escalated modes
are downgraded to
autounder a root daemon (server safety guard).
[0.1.5] — 2026-06-24
rcPrefixis a free-form slug, not alocal|dev|prodenum — the fleet grows past three machines.
[0.1.4] — 2026-06-23
- Discover live external sessions by process (ps scan), not file mtime — a desktop-app open no longer surfaces dead sessions as live.
[0.1.3] — 2026-06-22
- Add the bun bin dir to the daemon PATH (defense-in-depth for auto-update).
[0.1.2] — 2026-06-22
- Fix daemon auto-update preflight (bare
bunnot in the launchd PATH).
[0.1.1] — 2026-06-22
- Fix
ccmux installignoring--release-urlover an existing config.
[0.1.0] — 2026-06-22
- First public release: persistent self-healing Claude Code tmux sessions with deterministic resume, fleet daemon, TUI, GitHub Releases auto-update.