Skip to content

Releases: Open330/muxa

v0.5.0 — push-based watch + stuck-Working sweep

04 May 06:57
2f493cb

Choose a tag to compare

Minor release. muxa watch STATE column now updates in milliseconds instead of waiting for the 500 ms polling tick, and a new opt-in reconciler sweep recovers from missed Stop/TurnStopped hook firings.

What changed

User reported intermittent stale STATE in muxa watch. Investigation surfaced two distinct root causes — both fixed in this release.

Push-based updates via streaming Subscribe

The watch was polling on a hard 500 ms tick. New IPC `Subscribe` RPC: client opens a long-lived socket, daemon streams one JSON-encoded `Transition` per state change. Watch races the stream against a much slower fallback poll (5 s).

  • Hot path: state hits the screen in ~ms (was up to 500 ms)
  • Idle path: CPU drops to ~0 (was constant 2 Hz polling)
  • Fallback: Lagged drops or dead stream cleanly degrade to historical 500 ms polling
  • Back-compat: older daemons → silent fallback to polling, no client-visible breakage

Stuck-Working sweep

Push doesn't help when the agent never fires the event in the first place (Claude Code's Stop hook can drop on context-limit hits, mid-turn crashes, etc). New `[reconciler] stuck_working_timeout_secs` config: every reconciler tick auto-flips agents stuck in Working past the threshold to Idle and broadcasts a synthetic Transition so push subscribers see the correction live.

[reconciler]
stuck_working_timeout_secs = 300   # 5 min — reasonable for interactive use
# 0 (default) = disabled, preserves "state changes only on explicit events" guarantee

Performance

Push-based isn't a perf hit — it's a perf win on every axis except first-connection setup:

Polling (≤ v0.4.5) Push (v0.5.0)
agent CLI hook latency unchanged unchanged
daemon CPU (idle) constant 2 Hz request handling 0 (broadcast subscriber blocked on recv)
watch CPU (idle) 2 Hz wake + snapshot 5 s wake (push handles change cases)
state→screen latency up to 500 ms ~5 ms

Upgrading

git pull
cargo install --path crates/muxad --locked
cargo install --path crates/muxa-cli --locked

If you want to enable the stuck-Working sweep, add to `~/.config/muxa/config.toml`:

[reconciler]
stuck_working_timeout_secs = 300

PR: #23

v0.4.5 — launchd bootstrap race + fast path

02 May 08:55
9aaddb3

Choose a tag to compare

Patch release. Fixes the Bootstrap failed: 5: Input/output error reported on a macOS host where v0.4.2 had already wired the launchd plist and the user re-ran the wizard.

Fixed

  • muxa init no longer hits EIO from launchctl bootstrap when re-run against an already-installed launchd agent (#22). Two orthogonal changes:
    • 1500 ms sleep between bootout and bootstrap. launchd needs ~1-2 s to reap the previous agent's state before it'll accept a re-bootstrap into the same slot. The fresh-install path doesn't hit this delay because the new fast path below short-circuits.
    • Fast-path skip when the agent is already loaded. If launchctl print gui/<uid>/<label> succeeds, just kickstart -k to pick up any binary/config changes and return. The common idempotent re-run case is now both faster and entirely bypasses the EIO surface.

Upgrading

git pull
cargo install --path crates/muxad --locked
cargo install --path crates/muxa-cli --locked
muxa init --component muxad-launchd --yes   # idempotent, safe to re-run

If you previously hit EIO and manually bootstrapped the agent (the workaround that was floating around), v0.4.5's wizard now does the right thing on its own — no manual intervention needed.

PR: #22

v0.4.4 — defer to service manager + clearer preflight label

02 May 07:51
2923007

Choose a tag to compare

Patch release. Fixes the auto-restart gap reported on macOS during v0.4.3 testing — muxa init succeeded but pkill -9 muxad left muxad gone with no auto-restart, because our --start-daemon step was racing launchd's spawn and producing an unsupervised orphan.

Fixed

  • --start-daemon defers to the service manager (#21). StartDaemonIfNeeded is now suppressed when muxad-systemd or muxad-launchd is in the plan. Previously both fired during apply and our nohup muxad & won the socket-bind race against the manager's child — muxa status worked, but launchctl print revealed active count = 0 / state = spawn scheduled, and KeepAlive never kicked in. The shellrc-only path (where no real manager owns the lifecycle) keeps the action so muxa init leaves muxad responsive in the same session.
  • Pre-flight label· muxad already running was emitted even when muxad was not running (only the leading glyph flipped). Now state-dependent: ✔ muxad responding vs · muxad not running (will be started on apply).

Upgrading

git pull
cargo install --path crates/muxad --locked
cargo install --path crates/muxa-cli --locked

If you ran v0.4.2 / v0.4.3 and your auto-restart never kicked in:

pkill -9 muxad           # kill the orphan
muxa init --component muxad-launchd --yes   # macOS — re-bootstraps launchd
# or `muxad-systemd` on Linux

Verify auto-restart:

pkill -9 muxad ; sleep 12 ; muxa status     # should respond again
launchctl print "gui/$(id -u)/dev.open330.muxad" | grep -E "active count|state"
# active count = 1, state = running

PR: #21

v0.4.3 — IPC liveness + polling spawn

30 Apr 09:12
5685b97

Choose a tag to compare

Patch release. Fixes the same failure mode as the v0.4.0 incident, but caught at the wizard's daemon-liveness check rather than after the fact.

Fixed

  • Daemon liveness now probes the IPC socket, not pgrep (#20). Both pre-flight detection and the --start-daemon action's already-up short-circuit. The pgrep approach returned true for zombie processes whose listen socket had died — exactly the v0.4.0 stale-pid case where the wizard claimed muxad was running but muxa status still failed with daemon not reachable. Socket-connect captures the only thing that matters: "is the daemon answering."
  • Replaced the 300 ms post-spawn sleep with bounded polling (3 s timeout, 20 ms interval). systemd's enable --now and launchd's bootstrap return as soon as the spawn is initiated, not when the socket is bound — 300 ms worked on hot hardware and silently raced on slow VMs / CI / cold caches. Polling adapts: hot path completes in <20 ms, slow boots get the grace window.
  • locate_muxad now also checks /opt/homebrew/bin/muxad and /usr/local/bin/muxad after the cargo bin fallback, so a brew-installed muxad lands at the correct path on first install.

Internal

  • New init::util module hosts uid_string() (deduplicated from detect.rs + files/launchd.rs) plus the new default_muxad_socket() / muxad_responsive() / wait_for_muxad() helpers. Six new unit tests against a real UnixListener cover both the immediate-return and timeout paths with elapsed-time bounds.

Upgrading

git pull
cargo install --path crates/muxad --locked
cargo install --path crates/muxa-cli --locked

If you'd hit the v0.4.0/v0.4.2 false-positive pgrep case (wizard finished but muxa status failed), v0.4.3's muxa init will correctly detect the stale state and re-spawn muxad.

PR: #20

v0.4.2 — macOS launchd + shellrc fallback + --start-daemon

30 Apr 08:58
b8aad49

Choose a tag to compare

End-to-end install on every platform. v0.4.1 wired tmux/agent configs, but on macOS / containers / WSL1 the wizard finished with muxad not actually running. v0.4.2 closes that gap with a launchd component, a cross-platform shellrc fallback, and an explicit --start-daemon step.

TL;DR

muxa init                                    # interactive wizard, picks the right manager for your OS
muxa init --preset standard --yes            # one-shot, leaves muxad running
muxa init --start-daemon=false               # skip the auto-spawn (dotfile bootstrap)

Added

  • muxad-launchd — macOS analogue of muxad-systemd. Writes ~/Library/LaunchAgents/dev.open330.muxad.plist, runs launchctl bootstrap gui/<uid> + kickstart -k. Uninstall does the inverse.
  • muxad-shellrc — cross-platform fallback for hosts with no service manager (containers, BSDs, WSL1, minimal Linux, CI sandboxes). Auto-detects $SHELL to pick ~/.zshrc / ~/.bashrc / fish config / ~/.profile, drops a marker block that lazy-starts muxad from the next interactive shell.
  • --start-daemon flag (default true) — final action that spawns muxad detached if pgrep -x muxad doesn't find it. Belt-and-suspenders alongside the manager components: even if systemctl enable --now or launchctl bootstrap silently no-ops, the explicit start guarantees a responsive muxad when the wizard finishes.

Changed

  • Smart daemon-manager selection. Preset::Standard / Preset::Full and the wizard's multi-select now show exactly one daemon-manager candidate, picked from std::env::consts::OS. macOS users no longer see muxad-systemd in the picker; Linux users no longer see muxad-launchd. Detection::recommended_daemon_manager() further degrades to muxad-shellrc when systemctl/launchctl is missing on a host that'd normally support them — the "Standard preset = working install" invariant holds even in stripped-down envs.

Upgrading

git pull
cargo install --path crates/muxad --locked
cargo install --path crates/muxa-cli --locked
muxa init                  # picks the right manager automatically

If you already ran v0.4.1 and the daemon was never running, just rerun muxa init — the new --start-daemon step boots it.

PR: #19

v0.4.1 — muxa init wizard (safe release)

30 Apr 06:58
2e0537c

Choose a tag to compare

v0.4.1 is the canonical release of the muxa init install wizard. It bundles the new feature shipped in v0.4.0 with a critical safety fix. v0.4.0 is unsafe — do not use.

TL;DR

A single command replaces the previous five-step manual install:

# Existing checkout
muxa init                                    # interactive wizard
muxa init --preset standard --yes            # CI / dotfile bootstrap
muxa init --dry-run                          # preview, write nothing
muxa init --uninstall                        # surgical reverse

# Fresh machine
curl -fsSL https://raw.githubusercontent.com/Open330/muxa/main/scripts/install.sh | sh

What's in this release

✨ Added — muxa init install wizard (originally v0.4.0, PR #17)

Three entry points sharing one core:

  • Tier 1curl … | sh bootstrap (scripts/install.sh) builds binaries, hands off to the wizard.
  • Tier 2 — Interactive cliclack wizard (clack.cc-styled boxed UI: pre-flight ▸ multi-select ▸ review diff ▸ apply ▸ verify).
  • Tier 3--preset {minimal,standard,full}, --yes, --component, --no <id>, --dry-run, --uninstall, with MUXA_INIT_* / CI=true env-var fallbacks.

7 selectable components: tmux-popup, tmux-statusline, claude-hooks, codex-hooks, gemini-hooks, muxad-systemd, dashboard. Each owns either a # >>> muxa managed (id) >>> marker block (line-based files) or a command-prefix match (JSON/TOML), so --uninstall is a surgical reverse — hand-edited config around our blocks stays intact.

Best-practice baseline:

  1. Auto-detect → smart defaults (Claude config present → claude-hooks pre-checked)
  2. <file>.muxa-backup-<unix_ts> before any write
  3. Atomic temp-file + rename writes
  4. Idempotent — install/uninstall round-trip is lossless
  5. Same visual structure in non-interactive mode for CI logs
  6. Custom Claude statusLine (e.g. ccstatusline) detected → warning, file left alone

🐛 Fixed — muxa init no longer kills running tmux sessions (PR #18)

v0.4.0's post-apply verify step shelled out to:

tmux -f ~/.tmux.conf start-server \; kill-server

intending to spin up a throwaway tmux server, parse the conf, then tear it down. The -f flag scopes the config file but NOT the socketstart-server attached to the user's default socket and the trailing kill-server killed every running session there.

v0.4.1 removes the check entirely. Hardening with -L muxa-init-verify would patch the immediate bug, but the value/risk math doesn't justify keeping it: the check ran after we wrote the conf (couldn't prevent bad output), marker-block content is fixed in the binary (we control it), the user already approves the diff in the "Review changes" step before apply, and tmux source-file (which apply.rs runs) surfaces any real syntax error itself.

If you ran v0.4.0 and lost tmux sessions: sessions can't be recovered without tmux-resurrect / tmux-continuum. Apologies. The *.muxa-backup-<ts> files preserve your pre-init configs — restore from those if anything else looks off.

Upgrading from v0.3.x

git pull
cargo install --path crates/muxad --locked
cargo install --path crates/muxa-cli --locked
muxa init      # opt in via the wizard

Numbers

  • 15 new files, ~3.0 kLOC under crates/muxa-cli/src/init/
  • +146 unit tests (workspace 215 → 361)
  • Clippy clean with -D warnings, rustfmt clean
  • 4 platforms: x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-apple-darwin, aarch64-apple-darwin

PRs: #17 (feature), #18 (fix)

v0.4.0 (broken — use v0.4.1)

30 Apr 06:02
c1baff7

Choose a tag to compare

⚠️ DO NOT USE — UPGRADE TO v0.4.1

This release contains a critical bug: a post-apply verification step in
muxa init killed every running tmux session on the host. The fix in
v0.4.1 removes the
dangerous code path. Same wizard, no session loss.


Original v0.4.0 — muxa init install wizard

A single command replaces the previous five-step manual install (cargo install + hand-edit ~/.tmux.conf + jq-merge ~/.claude/settings.json + ~/.codex/config.toml + ~/.gemini/settings.json + systemd unit).

Three entry points sharing one core:

  • Tier 1curl -fsSL https://raw.githubusercontent.com/Open330/muxa/main/scripts/install.sh | sh boots muxa init after building binaries.
  • Tier 2muxa init interactive wizard via cliclack (clack.cc-style flow: pre-flight ▸ multi-select ▸ review diff ▸ apply ▸ verify).
  • Tier 3--preset {minimal,standard,full}, --yes, --component, --no <id>, --dry-run, --uninstall, with MUXA_INIT_* / CI=true env-var fallbacks for headless installs.

Components (7 selectable)

tmux-popup, tmux-statusline, claude-hooks, codex-hooks, gemini-hooks, muxad-systemd, dashboard. Each owns either a # >>> muxa managed (id) >>> marker block (line-based files) or a command-prefix match (JSON/TOML), so --uninstall is a surgical reverse — hand-edited config around our blocks stays intact.

Best-practice baseline

  1. Auto-detect → smart defaults (Claude config present → claude-hooks pre-checked)
  2. <file>.muxa-backup-<unix_ts> before any write
  3. Atomic temp-file + rename writes
  4. Idempotent — install/uninstall round-trip is lossless
  5. Same visual structure in non-interactive mode for CI logs
  6. Custom Claude statusLine (e.g. ccstatusline) detected → warning, file left alone

PR: #17

v0.3.2

30 Apr 06:50
66c6e1e

Choose a tag to compare

Release notes are populated from CHANGELOG.md after the matrix uploads finish.

v0.3.1

30 Apr 06:50
3131c38

Choose a tag to compare

Release notes are populated from CHANGELOG.md after the matrix uploads finish.

v0.3.0

29 Apr 08:45
7c1ff5c

Choose a tag to compare

Release notes are populated from CHANGELOG.md after the matrix uploads finish.