Skip to content
andyrewleePublic

Repository files navigation

Screenshot 2026-01-20 at 1 00 23 AM

TUI for easily running parallel coding agents

Latest release License Go version Discord

Quick start · How it works · Features · Configuration

amux TUI preview

What is amux?

amux is a terminal UI for running multiple coding agents in parallel with a workspace-first model that can import git worktrees.

Prerequisites

amux requires tmux (minimum 3.2) and git — workspaces are git worktrees, so a directory that is not inside a git repository cannot host one. Each agent runs in its own tmux session for terminal isolation and persistence.

The install script verifies release signatures with minisign — install it first, or set AMUX_ALLOW_UNVERIFIED=1 to proceed with checksum-only verification. The Homebrew and go install paths don't need it.

Quick start

brew tap andyrewlee/amux
brew install amux

Or via the install script:

curl -fsSL https://raw.githubusercontent.com/andyrewlee/amux/main/install.sh | sh

Or with Go (requires Go 1.26 or newer; contributors should use the patched toolchain pinned in go.mod):

go install github.com/andyrewlee/amux/cmd/amux@latest

Then run amux to open the dashboard.

How it works

Each workspace tracks a repo checkout and its metadata. For local workflows, workspaces are typically backed by git worktrees on their own branches so agents work in isolation and you can merge changes back when done. The create dialog (press enter on a project's + New row) takes the workspace name plus an optional base ref — branch, tag, or commit the new branch is created from; leave it empty to use the repo's detected default branch. If the requested branch already exists, amux attaches the worktree to it instead of failing — and if creation then fails partway, rollback removes the worktree it made but never deletes a branch it did not create.

Both write actions are available from the UI, each behind an explicit confirmation:

  • Commit — press c in the Changes sidebar to stage everything in the workspace and commit it with a message you type. The commit lands on the workspace's own branch; amux never pushes.
  • Merge — press M on a workspace row in the dashboard to merge its branch into its base with git merge --no-ff. The merge happens in the project's primary checkout, so amux checks that the base branch is checked out there before asking, then re-verifies it at the moment you confirm — if the checkout moved (or detached) while the dialog was open, the merge refuses rather than landing on the new HEAD or moving it for you. On a conflict it lists the conflicted files and offers to abort; resolving them is yours to do in the terminal.

Note that amux runs git with repository hooks disabled (see AMUX_ALLOW_GIT_HOOKS below), so your own pre-commit or pre-merge hooks will not fire on these actions.

Shelving puts a workspace away without deleting it: press S on a live workspace row in the dashboard to shelve it — the worktree is removed, while the branch, workspace metadata, and scripts are kept. Shelve runs the repo's archive hook first if one is configured (it's a repo-supplied command, so the usual trust gating applies), and it's best-effort like delete: a failed archive never strands the shelve. Shelved workspaces list in their own dashboard section — Enter on a shelved row restores it (recreates the worktree and re-runs setup-workspace), and D purges it for good (deletes the branch and the record behind a confirmation).

Delete and shelve share one teardown order: any in-flight setup-workspace sequence and detached on-done hooks are canceled and drained first, the run script is stopped, and only then does archive run and the worktree go away — a lifecycle subprocess can never outlive the directory it writes into. While teardown is in progress the workspace refuses new lifecycle starts.

For cleaning up a fleet at once, space marks a workspace row (●) and esc clears marks; S with marks present shelves the marked set after one confirmation listing the names — each workspace shelves sequentially through the same guarded path a single shelve uses, and a summary toast reports the outcome when the batch drains.

Architecture quick tour

Start with ARCHITECTURE.md for the repo-level package map and dependency direction. Then internal/app/ARCHITECTURE.md covers lifecycle, PTY flow, tmux tagging, and persistence invariants, and internal/app/MESSAGE_FLOW.md documents message boundaries and command discipline.

External orchestration: the supported tmux-level contract is documented in docs/ORCHESTRATION.md.

Features

  • Parallel agents: Launch multiple agents within main repo and within workspaces
  • No wrappers: Works with Claude Code, Codex, OpenCode, Droid, Cursor, Pi, Oh My Pi, Antigravity, FX, Grok, Amp, Cline, Devin, and Prime Agent
  • Keyboard + mouse: Can be operated with just the keyboard or with a mouse
  • All-in-one tool: Run agents, view diffs, and access terminal

Controls

Ctrl-Space opens the command palette at the bottom of the screen (the toolbar button does the same; Esc cancels). Commands are short key sequences:

Sequence Action
q quit (confirmation dialog)
a add project
d delete workspace
S Settings
K cleanup tmux sessions
h / l focus pane left / right
n jump the dashboard cursor to the next workspace needing attention — a done badge or an agent that rang BEL (agents like Claude Code ring it on notification/permission events)
e event log — a read-only viewer listing this session's notifications (every toast the app showed), newest-first; scroll//search/y copy like the other output viewers
t a new agent tab
t t new terminal tab
t n / t p next / previous tab
t x close tab
t d detach tab (keeps the tmux session)
t r reattach detached tab
t s restart tab
t y copy transcript (scrollback + screen) of the focused terminal tab
t f save the focused terminal tab's full transcript to a file (default ~/.amux/transcripts/)
t o browse saved transcripts (~/.amux/transcripts/) and open one in the file viewer tab (requires a selected workspace)
1–9 jump to center tab by position

Detach, reattach, and restart are fenced per attach attempt: only the latest attempt's outcome applies to a tab, an explicit t d while an attach is in flight wins over the pending result, a duplicate t r reports "already in progress" instead of spawning a second client, and an attach that stalls past its timeout is released so it can be retried safely — a late result from the abandoned attempt is discarded, not applied over the newer terminal.

When a center terminal tab is focused, d instead scrolls down and u scrolls up — the palette shows the substitute meanings in that context. Prefix + prefix again sends a literal Ctrl-Space (NUL) to the focused terminal.

Center-pane keys (when a center terminal tab is focused — these are not global; a focused sidebar terminal forwards every key, including these chords, to the hosted shell):

  • Ctrl+C in a terminal tab sends the agent's interrupt sequence (agents that need more than one ^C, like Claude, get the configured count) — it is not a quit key; quit is C-Space q.
  • Ctrl+N / Ctrl+P (or Ctrl+]) cycle center terminal tabs; Ctrl+W closes the current one. Unreserved control chords reach the focused terminal as their bytes — Ctrl+Q arrives as 0x11 in either pane, and Ctrl+] arrives as 0x1D in a focused sidebar terminal (center keeps it for tab cycling).
  • PgUp / PgDn scroll the focused terminal's scrollback (a quarter page in the center pane, a half page in a sidebar terminal); the mouse wheel scrolls whichever pane the pointer is over (Shift+wheel forces amux scrollback even when the hosted app claims the wheel).
  • Left-drag selects text in a center terminal tab; releasing copies the selection to the system clipboard (the same selection-drag works while scrolled into scrollback). The hosted app never sees the drag — selection is amux-side, so it works over agents that capture mouse input too.

Dashboard rows (workspace/project/shelved list):

  • j/k or arrows move the cursor; PgUp/PgDn or Ctrl+U/Ctrl+D move a half page; g/G jump to the first/last row. j past the last row (or down) focuses the bottom toolbar — h/l move there, enter runs the highlighted action, k/up returns to the rows.
  • enter opens the row: expands a project, activates a workspace, restores a shelved workspace. With marks present it bulk-restores the marked shelved set.
  • D deletes the row (each behind a confirmation): deletes a live workspace, removes a project, purges a shelved workspace. With marks on live rows it bulk-deletes them; with marks on shelved rows it bulk-purges. Bulk deletes are a typed confirm — type the workspace count — and drain each workspace through the same guarded path a single delete uses.
  • R renames a workspace; M merges its branch into the base (see How it works) — single-row by design since a merge can stop on conflicts; on success a follow-up offers to shelve or delete the now-merged workspace; S shelves it (bulk-shelves the marked set).
  • space marks a row (●); esc clears marks, or dismisses an applied filter when there are no marks.
  • r rescans — refreshes the list and imports worktrees created outside amux as workspaces.
  • / filters the list: workspace and shelved rows match on name, path, or branch; a project header survives when the project itself or any of its rows matches (a surviving project keeps its + New row). While typing, every key is filter text; enter applies, esc clears.

Sidebar (Changes tab, focused):

  • 1 / 2 switch the sidebar between Changes and Project views.
  • j/k or arrows move, enter/space/o opens a diff, v opens the file itself in the center pane's editor/viewer, g refreshes, / filters.
  • c commit, b branch mode, e workspace env, E project env, s scripts, r run/stop the workspace run script, R run output, O script output, u re-run setup, i workspace status — which also reports a cleanup section when an interrupted delete left a tombstone (startup recovery retries the finish automatically on next load). When script_mode: concurrent has produced multiple run sessions, R opens a picker (#1 is the first run, -2/-3/… numbered runs after) listing each session's live/exited status — pick one to view its output. Inside the live run-output viewer, a attaches an interactive tab to the viewed session (closing the tab detaches — the script keeps running). All output viewers (R, O, i) share the same scroll keys: j/k scroll, PgUp/PgDn page, g/G jump to the top/bottom (G resumes following), f toggles follow mode, y copies the retained snapshot to the clipboard, esc/enter closes. The R and O transcript viewers also support / to search (literal, case-insensitive; only the retained buffer is searched — scrollback dropped from the tail is not), with n/N jumping between matches. While typing the query every key is literal text; enter accepts and jumps to the first match, esc exits the query field (a second esc closes the viewer). The i status panel is not searchable.
  • Workspace lifecycle keys (S shelve, space mark, esc clear marks) are on dashboard rows — see How it works.

Sidebar (Project tab, focused):

  • j/k or arrows move, l/h (or arrows) expand/collapse directories, enter/o opens a file in the center pane, . toggles hidden files, r refreshes. Directory reads run in the background — a loading directory shows a … marker and a failed read shows a short error you can retry with r — so a slow filesystem never freezes input or other panes.
  • / filters the loaded tree: matching entries stay visible together with their ancestor directories (a collapsed parent is not expanded, it just stays on screen as the match's path context). Expansion state is never touched, so esc restores the tree exactly. Only loaded entries can match — children of a directory you never expanded are not in the tree yet. While typing, every key is filter text (digits included — they do not switch sidebar tabs); enter applies, esc clears.

Diff viewer (opened from Changes with enter/space/o):

  • j/k/arrows and the wheel scroll, PgUp/PgDn (or ctrl+u/ctrl+d) page, g/G jump to the top/bottom. With w wrap enabled, scrolling moves display rows so a wrapped line's tail is always reachable; the footer counts display rows, not source lines.
  • n/p cycle forward/back through diff hunks (still source-line anchored).
  • v opens the diffed file in the center pane's editor/viewer — the same jump the Changes list's v makes. Only regular files inside the workspace open: a repo-shipped symlink, FIFO, or out-of-root path is refused with a warning toast rather than rendered.
  • / opens a literal, case-insensitive search over the diff lines (+/- markers and @@ headers included). enter accepts, esc leaves the field with the query kept; n/N then cycle matches (wrapping, with the selected match shown in reverse video and scrolled to the wrapped segment carrying it, with the enclosing hunk header kept in view when it fits) — n falls back to hunk navigation while no match is selected. A browse-mode esc clears the query before closing; q still closes outright.
  • Resizing or toggling w keeps the same source line at the top of the view when possible; q/esc closes the tab.

Text fields in the env, scripts, and Settings editors accept terminal bracketed paste: only the first pasted line is appended to the focused field (later lines and control bytes are dropped — these are single-line fields), and paste never submits, cancels, or toggles a row.

Inside those editors, up/down move between rows, enter saves, and esc cancels. The env editor adds a row with ctrl+a (a two-field name→value input where tab/shift+tab switch fields) and removes the selected row with ctrl+d; the scripts editor toggles a row's run mode with space; the Settings assistants list also adds with ctrl+a. Note ctrl+d means remove here, not page-down — dialogs own their keys while open.

Picker keys: arrows and tab/shift+tab navigate every list picker. The agent picker additionally fuzzy-filters as you type (printable keys are filter text there); unfiltered pickers like the run-session list take j/k as navigation.

On the agent picker, ctrl+t picks the highlighted assistant but routes through an optional first-task field before launching: the text you enter is queued on the tab and typed into the agent once its TUI reports readiness (first private-mode set — the same delivery a tmux send-keys seeding script approximates, with the wait handled for you). Sent as keystrokes plus Enter, never as argv. An empty field launches exactly like a plain enter pick; esc backs out to the picker. The key is inert where the pick feeds a flow with no task carrier — the assistant pick inside workspace creation.

Path pickers (add project, transcript browser) fuzzy-filter the listed rows as you type a name, but an explicit path takes precedence over the highlighted row: an absolute path, ~, ~/…, ./…, ../…, or any input containing a separator makes enter resolve that path directly and tab navigate into a directory — it never opens an unrelated listed row. A plain name (or one more component after the prefilled directory) still filters and selects rows.

Set "ui": { "show_keymap_hints": true } in ~/.amux/config.json to show an in-app hint bar.

Self-update: amux checks for a new release at startup; when one is available a self-update item appears in Settings (C-Space S). Updates are minisign-verified before being applied. The install step never leaves the binary path empty: it backs the current executable up by copy, then replaces it with a single atomic rename — if the update is interrupted, either the old or the new binary is reachable at the installed path. In the rare case the post-replacement durability check fails, the error names the retained backup and the mv command to restore it.

Configuration

Create .amux/workspaces.json in your project to define commands that amux runs for its workspaces:

{
  "setup-workspace": [
    "npm install",
    "cp $ROOT_WORKSPACE_PATH/.env.local .env.local"
  ],
  "run": "npm start",
  "on-done": "osascript -e 'display notification \"agent done\"'",
  "archive": "tar -czf archive.tar.gz .",
  "env": {
    "GOPRIVATE": "git.example.com/*"
  }
}
  • setup-workspace — commands run once when a new workspace is created. If a run fails transiently or you edit the config later, press u in the Changes sidebar to run it again (repo-supplied commands re-check trust first).
  • run — the command started for a workspace's run script. Press r in the Changes sidebar to start it, and r again to stop it; a [run] marker sits next to the branch name while it is live. The script runs in its own tmux session, so its output survives the process: press R to view the captured tail (live while running, or after it exits — a crashed run keeps its output and reports the exit status in a toast). Bind your dev server to AMUX_PORT so parallel workspaces don't collide.
  • on-done — a fire-once hook run when one of the workspace's agent sessions crosses the working→done edge (the same transition that rings the notify_on_done bell and posts the "Agent finished" toast). It runs detached in the workspace root with AMUX_SESSION set to the session name. Only a strict working→done transition fires it — an agent that was already finished when amux started does not trigger the hook. When several sessions finish in the same scan every eligible hook is attempted once: a dispatch failure reports its own warning and never blocks the rest, and completed edges are not replayed. Hooks are bounded work: at most 8 may run at once per workspace (later edges dispatch again once slots free) and each is killed after two minutes, so a runaway hook can't pin workspace teardown.
  • archive — the command run when a workspace is archived, i.e. just before its worktree is deleted. It runs to completion (up to two minutes) in the worktree while that directory still exists, after every lifecycle subprocess has been stopped and drained. It is best-effort: if it fails or the repo isn't trusted yet, amux warns you and deletes the workspace anyway, so a broken archive script can never strand a workspace. Write it to tolerate running twice — if the delete itself fails after the script has run, retrying the delete runs it again.

setup-workspace, archive, and on-done each record a bounded tail of their combined output on every run — press O in the Changes sidebar to view the last run of each for the shown workspace. While a setup is in-flight the same viewer shows that run's live tail under a ── setup ── running section and refreshes until it finishes, so a long npm install is visibly working rather than looking hung; once it ends the recorded transcript replaces the live section and the viewer goes static again. Transcripts persist under the workspace's metadata dir (script-transcripts.json), so O still answers "why did setup fail" after a restart; writes merge latest-per-type across amux processes, so a restarted amux recording one hook can't erase the others. Persistence is best effort — a corrupt or newer-schema envelope is left untouched rather than overwritten, and transcripts are never written for a workspace whose record is gone. A failing on-done hook also posts a warning toast per failed dispatch (it used to be invisible); R stays the live surface for run.

For the full operational picture press i in the Changes sidebar: a read-only snapshot of the workspace's reserved port range (read live from the durable registry — a workspace with no reservation yet, or a corrupt registry, says so rather than guessing), run-session state, script config (repo vs. workspace vs. project layer) and its trust verdict, custom env key names (never values), lifecycle state, and open tabs.

You can also set per-workspace scripts yourself: press s in the Changes sidebar to open the workspace scripts editor, which stores setup, run, archive, and on-done commands on the workspace record plus a run mode (nonconcurrent stops a previous run before starting a new one; concurrent lets them overlap). A repo-provided .amux/workspaces.json script still wins over the per-workspace one when both exist — the editor's fields are the fallback. Commands entered here are your own input, so they run without the repo trust prompt described below.

A third, per-project layer sits beneath both: ~/.amux/project-scripts.json maps each project (keyed by repo path) to setup/run/archive/on-done defaults every workspace in that project inherits. Precedence per script is repo .amux/workspaces.json → workspace record → project defaults — the strongest layer that defines the script wins. The file is hand-edited (there is no project-script editor yet), re-read on every resolution so changes take effect without a restart, and user-authored like the workspace layer — no trust prompt. The i status dialog labels which layer each script comes from (repo/user/project).

Environment available to workspace scripts

setup-workspace, run, and archive scripts run with these variables set, in addition to your normal shell environment:

Variable Meaning
AMUX_WORKSPACE_NAME The workspace name
AMUX_WORKSPACE_ROOT The workspace worktree path
AMUX_WORKSPACE_BRANCH The workspace's git branch
ROOT_WORKSPACE_PATH The source repository root (also shown in the example above)
AMUX_PORT An allocated per-workspace port — bind dev servers here to avoid collisions across parallel workspaces
AMUX_PORT_RANGE The start-end port range allocated to this workspace

Port assignments are durable: the first spawn that needs a workspace's range commits it to ~/.amux/port-reservations.json under the workspace's persisted ID, and every later spawn — run/setup/archive/on-done scripts, agent sessions, sidebar terminals, after a quit, crash, or a second amux instance on the same state home — receives exactly that interval rather than a fresh draw. The configured base/width shapes only new reservations; an interval minted under earlier settings keeps its original bounds. Reservations are never reclaimed automatically — a workspace keeps its range even after its sessions end — and when no disjoint interval remains, amux reports a typed exhaustion error instead of silently reusing one. The i workspace-status dialog reports how many reservations are held by workspace IDs that no longer exist (no live, shelved, or in-flight record, no tagged session — a shelved workspace keeps its range until it is restored or fully deleted); R there opens a typed-confirm release — type the displayed count to proceed, then a fresh probe re-derives the orphans before deleting, so a changed set may release fewer or none — never automatic. Treat the registry as live state: do not delete port-reservations.json while any amux session exists.

On top of those injected values, custom environment layers in for all script spawns (setup-workspace, run, on-done, archive), lowest to highest precedence:

  1. Repo env — the .amux/workspaces.json map above (trust-gated).
  2. Project env — per-repo variables you own, shared by every workspace of the project. Press E in the Changes sidebar to edit; stored user-side in ~/.amux/project-env.json, so it never touches the repo and is the right home for secrets (API keys, tokens). No trust prompt — it is your input.
  3. Workspace env — per-workspace variables via e in the Changes sidebar. Always wins.

For ordinary keys (e.g. GOFLAGS) the highest layer's value wins outright — amux replaces rather than merges. PATH is the exception in interactive sessions: agents and sidebar terminals always get amux's augmented PATH — the inherited path plus the tool directories amux adds — appended after every user layer, so a layer cannot shadow it. The injected AMUX_*/ROOT_* names are reserved: custom layers can never override them.

Agent sessions and sidebar terminals get the same injected variables plus the two user-controlled layers — project env and workspace env — so an agent sees AMUX_PORT, your project secrets, and per-workspace overrides exactly as a run script would. The one deliberate narrowing: the repo env layer does not apply to interactive sessions, even for a trusted repo. Repo env is repo-chosen content gated for one-shot scripts; a long-lived agent or shell acting with your credentials is a bigger blast radius, so approving a repo's scripts never widens what the repo can inject into your agents.

Because these commands come from the repository, amux runs them only after you trust the repo. The first time a repo's .amux/workspaces.json would run (and every time its contents change), amux records the approved content of the file; until then those project-supplied scripts are skipped and you are notified, rather than executing arbitrary commands chosen by the repo's author. Editing .amux/workspaces.json invalidates the approval, so changed commands are re-gated until you trust the file again. To revoke an approval outright, open the workspace status dialog (i) and press U, then type untrust to confirm — the recorded grant is dropped and the next run re-prompts. (Run/archive scripts you enter yourself in the amux UI are your own input and are never gated.)

Workspace metadata is stored in ~/.amux/workspaces-metadata/<workspace-id>/workspace.json, and local worktree directories live under ~/.amux/workspaces/<project>/<workspace>. Field writes are transactional and narrow: renaming a workspace, editing its env or scripts, shelving/restoring it, and debounced tab-state saves each rewrite only their own fields inside the record lock, so concurrent edits merge instead of a delayed write resurrecting a stale snapshot. Trusted-repo approvals are recorded in ~/.amux/trusted-scripts.json, and the project-level environment map in ~/.amux/project-env.json and per-project script defaults in ~/.amux/project-scripts.json (hand-edited — nothing in the UI writes it yet). These registries share one write boundary: a file that exists but cannot be read refuses the save or approval and leaves its bytes untouched — a blind write could erase other projects' state — while a missing file initializes normally and corrupt JSON is still replaced by the authoritative write.

Assistants: the AI agents amux can launch are configured per-user in ~/.amux/config.json. You can add your own or override a built-in — see docs/CONFIG.md. A launch uses the assistant settings captured when it was requested, so saving new settings affects later launches, not panes already launched or attached. Saves preserve your effective interrupt settings — an explicit zero interrupt_delay_ms (no spacing between Ctrl-C signals) stays zero rather than falling back to a built-in default.

Saving never clobbers a config file amux cannot read: if config.json exists but is unreadable, malformed, or a non-object document, the save is refused and the file is left untouched so hand-edited sections survive. A missing or empty file is written normally — that's absence, not rejection. If a save fails the values you confirmed stay active in memory for the session, and amux remembers which section still owes a write: reopening Settings and confirming — even with no new edits — retries only the sections that failed (theme/interface/tmux together, assistants independently). Esc discards the open dialog's unconfirmed edits but never forgets a save obligation you already confirmed.

Platform Support

AMUX requires tmux and is supported on Linux/macOS. Windows is not supported.

Development

git clone https://github.com/andyrewlee/amux.git
cd amux
./scripts/install-hooks.sh   # one-time: enables the pre-commit + pre-push git hooks
make lint-tools              # one-time: builds the pinned golangci-lint into ./.cache/bin
make run

Run ./scripts/install-hooks.sh once after cloning. It points core.hooksPath at .githooks, enabling the pre-commit fmt/lint/file-length checks and the pre-push strict-lint gate the project relies on for quality.

Run make lint-tools once before your first make devcheck or git commit. It builds the linter pinned in .golangci-version into the gitignored ./.cache/bin; a stock golangci-lint from PATH may be a different version than the pinned one and produce different diagnostics. See LINTING.md and CONTRIBUTING.md for details.

Operations

  • Logs are written to ~/.amux/logs/amux-YYYY-MM-DD.log (default retention 14 days). Override retention with AMUX_LOG_RETENTION_DAYS.
  • Log verbosity: set AMUX_LOG_LEVEL=debug (accepts debug/info/warn/error; default info) to change what gets written to the log — debug is the first thing to try when reporting or diagnosing a problem.
  • Attached-tab limit: set AMUX_MAX_ATTACHED_AGENT_TABS (default 6; 0 disables the limit) to change how many agent tabs keep live PTYs attached concurrently.
  • Terminal-tab limit: set AMUX_MAX_ATTACHED_TERMINAL_TABS (default 6; 0 disables the limit) to change how many sidebar terminals keep live PTYs attached; least-recently-used background terminals detach automatically, stay alive in tmux, and re-attach when their workspace is selected.
  • Git hooks: amux runs git with repo hooks, core.fsmonitor, and commit/merge/tag GPG signing disabled (signing would run a repo-configured gpg.program) so a checked-out repository cannot execute code just because amux touched it; set AMUX_ALLOW_GIT_HOOKS=1 if your workflow needs repo hooks to run. amux also drops inherited GIT_* process/config variables (GIT_DIR, GIT_CONFIG_*, and friends) so a parent git process cannot inject state — user vars like GIT_SSH_COMMAND and GIT_AUTHOR_* are kept. Git invocations pin --work-tree to the directory they run in, so a delivered core.worktree cannot redirect merge/abort/writeback writes elsewhere, and a commondir file forged inside a checkout's .git directory is refused outright. Repo-configured drivers that would execute during amux's own git operations are refused rather than run: an attributed merge.*.driver blocks the merge, and a repo-configured filter.* driver (clean/smudge/process/command) attributed by the current index OR the checked-out/incoming tree (check-attr --source reads the tree a worktree add or merge writes) blocks workspace create/attach, add -A writeback, merges, and merge --abort — the refusal names the driver and is lifted by the same AMUX_ALLOW_GIT_HOOKS=1 opt-out, which is opting into repo trust. Filter drivers are additionally neutralized on EVERY git invocation (including status/diff reads, which run clean filters on stat-dirty files): amux injects empty -c filter.<name>.*= overrides for any driver configured in repo-local/worktree/included config, so nothing repo-configured can execute even where no gate exists. Only repo config scopes are enumerated or neutralized, so git-lfs works either way — globally configured clean/smudge filters are untouched, and amux points core.hooksPath at /dev/null (not at an empty value) so lfs cannot leave stray hook files in a workspace root. Merge drivers are enumerated across all config scopes — a repo's .gitattributes can name a globally configured driver, and refusing only costs a manual merge.
  • Git cancellation: every git invocation runs in its own process group (Unix) and has a bounded output drain, so a timed-out or canceled git operation terminates spawned descendants too — a lingering child process cannot hold a worktree lock or output pipe open past the deadline plus a short cleanup allowance.
  • OSC 52 clipboard: set AMUX_ENABLE_OSC52_CLIPBOARD=1 to let agent terminal output copy to your clipboard via OSC 52 (off by default because terminal output is untrusted; payloads over 64 KiB are ignored).
  • Perf profiling: set AMUX_PROFILE=1 to emit periodic timing/counter snapshots; adjust cadence with AMUX_PROFILE_INTERVAL_MS (default 5000).
  • pprof: set AMUX_PPROF=1 (or a port like 6061) to expose net/http/pprof on 127.0.0.1.
  • Debug signals: set AMUX_DEBUG_SIGNALS=1 and send SIGUSR1 to dump goroutines into the log.
  • PTY tracing: set AMUX_PTY_TRACE=1 or a comma-separated assistant list; traces write to the log dir (or OS temp dir if logging is disabled). The trace captures both directions of the pipeline — agent→amux output is tagged RECV and amux→agent input (keystrokes, pastes, the delayed Enter/CR) is tagged SEND — so send-path issues like a dropped Enter can be debugged at the byte level.
  • Contributor hook flags: AMUX_SKIP_LINT and AMUX_SKIP_HARNESS scope themselves to their named gates in .githooks/ — skipping lint still runs formatting, file-length, harness, and e2e checks; skipping the harness still runs lint, e2e, and the tmux check. They are dev-side only (see CONTRIBUTING.md); the amux app never reads them.

Releases

Packages

Contributors

Languages