Contributing to amux
July 12, 2026 · View on GitHub
Development
git clone https://github.com/andyrewlee/amux.git
cd amux
./scripts/install-hooks.sh
make lint-tools # one-time: builds the pinned golangci-lint into ./.cache/bin
make run
Run make lint-tools once before your first make devcheck or git commit.
It builds the linter version pinned in .golangci-version into the gitignored
./.cache/bin; a stock golangci-lint from PATH may be a different version
from CI and produce different diagnostics. See LINTING.md for the
full rationale.
The minimum supported Go family is 1.26 (the go directive in go.mod).
The toolchain directive in go.mod pins the patched Go 1.26 toolchain used
for local checks, CI, and releases. With the standard GOTOOLCHAIN=auto
setting, the go command switches to that patched toolchain automatically. If
you force GOTOOLCHAIN=local, install the pinned patch release yourself before
running repo checks.
Run the fast local checks:
make devcheck
make devcheck is the required pre-PR gate: it runs vet, tests, and lint (including file-length checks). It is the fast subset of CI, not the whole of it — CI additionally enforces the race detector and go mod tidy cleanliness. For the full local CI mirror, run:
make ci
make ci runs devcheck plus make test-race (CI's race gate; slow), make tidy-check (CI's tidy gate), and make govulncheck (CI's vulnerability scan, using the same pinned govulncheck version — it also works standalone to reproduce a CI vuln failure). CI's harness smoke steps are not mirrored locally.
For the inner loop, launch the TUI with make run in a real terminal — amux requires stdin, stdout, and stderr to all be TTYs, so it only runs directly in your terminal. air cannot host the TUI: it launches the rebuilt binary with stdin on /dev/null, which fails that TTY check, so make dev is not a hot-reload TUI loop. Use it instead for automatic rebuilds and compile-error feedback while you edit — run make dev in a second pane alongside make run. It runs air with the repo's .air.toml and rebuilds on save. Install it once with:
go install github.com/air-verse/air@latest
Ensure $(go env GOPATH)/bin is on your PATH; otherwise make dev prints this same install hint and exits.
For style-only cleanup, run:
make fmt
Before opening larger PRs, also run strict ratcheted lint on changed code:
make lint-strict-new
Pull requests are CI-gated (automated). For local confidence before opening a PR:
- always:
make devcheck,make lint-strict-new - if touching concurrency (supervisor workers, PTY read loops, watchers, activity leases, anything with goroutines/channels/mutexes):
make test-race— CI runs the race detector andmake devcheckdoes not, so this is the most common green-local/red-CI surprise - after any dependency change (adding/removing an import, editing
go.mod):make tidy-check— CI fails on an untidygo.mod/go.sumeven whendevcheckpasses - before opening a PR you want green on the first push:
make ci(devcheck + test-race + tidy-check, the full local CI mirror; slow) - if touching
internal/ui/,internal/vterm/, orcmd/amux-harness/:make harness-presets - if touching
internal/tmux/,internal/e2e/, orinternal/pty/:go test ./internal/tmux ./internal/e2e - if touching the agent input/send path (
internal/pty/terminal.go,internal/ui/center/tab_actor_write.go,internal/pty/, agent keystroke forwarding):make verify-loop— proves a real agent receives keystrokes end-to-end (incl. a literal CR);make devcheckdoes not, since the real-tmux tests skip there
Architecture references:
ARCHITECTURE.md(repo-level package map and dependency direction)internal/app/ARCHITECTURE.mdinternal/app/MESSAGE_FLOW.md
Harness
cmd/amux-harness renders the real UI without a TTY for deterministic perf and
render checks. make harness-presets runs heavier local confidence presets for
center/sidebar/monitor. CI uses shorter direct invocations; to reproduce a CI
failure, run the matching mode with the CI shape, e.g. center:
go run ./cmd/amux-harness -mode center -frames 5 -warmup 1 -tabs 8 -width 160 -height 48 -hot-tabs 2 -payload-bytes 64 -newline-every 4
Inspecting a rendered frame
To see exactly what the UI rendered (instead of guessing), dump the final frame
with -dump-frame:
go run ./cmd/amux-harness -mode center -frames 1 -warmup 0 -dump-frame /tmp/frame.txt
The file contains the raw ANSI bytes the agent sees — cat /tmp/frame.txt to
eyeball it, diff two dumps to spot a regression, or feed it into a golden.
Rendering an overlay
Adding or altering a dialog/overlay is the most common UI change. The harness can
put the App into an overlay state so the frame exercises composeOverlays
instead of only the base pane. Pass -overlay (or set HarnessOptions.Overlay):
go run ./cmd/amux-harness -mode center -frames 1 -warmup 0 -overlay dialog -dump-frame /tmp/frame.txt
Supported overlays are the deterministic, filesystem-independent ones:
dialog (confirm dialog), settings (settings dialog), prefix (prefix
command palette), error (the error overlay), and input (input dialog). The
file picker (reads the real filesystem) and the toast (wall-clock-gated
visibility) are intentionally excluded because their frames are not byte-stable. Each overlay has a golden frame
(internal/app/testdata/golden/overlay_*.frame) guarded by
TestHarnessGoldenFrames; regenerate after an intentional overlay render change
with go test ./internal/app -run Golden -update and commit the refreshed
testdata.
See go doc ./cmd/amux-harness for all -mode values, flags, and the
AMUX_PPROF profiling hook.
Release
Versioning follows SemVer and tags are vX.Y.Z. Pushing a tag triggers the GitHub Actions release job.
Fast path:
git pull --ff-only
make release VERSION=v0.0.5
Manual steps:
make release-check
git tag -a v0.0.5 -m "v0.0.5"
git push origin v0.0.5
Notes:
make releaserunsrelease-check, creates an annotated tag, and pushes it. The worktree must be clean.- Release builds use the commit timestamp for
main.date, which keeps the timestamp deterministic for a given commit. If you need strict bit-for-bit reproducibility, consider adding-trimpathand a stable build ID to the build flags.
Homebrew tap
The Homebrew tap lives in andyrewlee/homebrew-amux and auto-bumps the formula after a release.
- After
make release VERSION=vX.Y.Z, the tap workflow updatesFormula/amux.rb(daily at 06:00 UTC). - To update immediately, run the Bump amux formula workflow in the tap repo.
- Users upgrade with
brew upgrade amux.