Install
Harness is one Go binary, harness, that is both the daemon and the client.
Pick one of the two methods below, then verify with harness doctor.
Homebrew (macOS and Linux)
brew install --HEAD stump-wtf/tap/harness
Installing by the fully qualified name is a one-liner and the only form that
works out of the box: since Homebrew 6.0.0
non-official taps require explicit trust, and a fully qualified name trusts just
that one formula rather than the tap and everything it may ever contain. The
older brew tap stump-wtf/tap + brew install harness form now fails, because
the short name needs the tap loaded and the tap is untrusted.
--HEAD builds the latest main from the
GitHub repository. The tap's tagged
formula can lag behind, and these guides use features that only exist on main.
The formula compiles from source, so Homebrew pulls in Go as a build dependency
and there is no Gatekeeper prompt on macOS.
To update a --HEAD install later:
brew upgrade --fetch-HEAD stump-wtf/tap/harness
From source
You need Go 1.26 or newer. An older Go (1.21+) downloads the right toolchain automatically the first time you build.
git clone https://github.com/stump-wtf/harness.git
cd harness
go install ./cmd/harness
That puts harness in $(go env GOPATH)/bin, usually ~/go/bin; make sure
that is on your PATH. Alternatively, make install builds with version
metadata baked in and installs to ~/.local/bin.
:::caution Use the clone, not go install …@latest
The module's import path is not its GitHub URL, so installing it by module path
fails — the mirror is a byte copy, and its go.mod still declares the original
path:
$ go install github.com/stump-wtf/harness/cmd/harness@main
go: github.com/stump-wtf/harness/cmd/harness@main: version constraints conflict:
github.com/stump-wtf/harness@v0.3.1-…: parsing go.mod:
module declares its path as: <the module's own path>
but was required as: github.com/stump-wtf/harness
Clone and build from the checkout as shown above, which sidesteps the module path entirely. Every dependency resolves from the public Go module proxy.
:::
Verify
harness --version
harness doctor
On a fresh install, doctor fails two checks, and both are expected. It prints
a hint for each, plus a SETTING table showing where every setting came from:
CHECK STATUS DETAIL
config error not found at /home/you/.config/harness/harness.toml
→ create one (see `harness daemon -h`) or pass --config PATH
daemon error unreachable at /run/user/1000/harness.sock
→ start it with: harness daemon
summary 0 passed · 0 warning(s) · 2 failed
SETTING SOURCE VALUE
socket default /run/user/1000/harness.sock
config default /home/you/.config/harness/harness.toml
…
The binary works. There is simply no config and no daemon yet. The next two
guides add both, after which every check should read ok.
To take it for a spin before setting up a service, run the daemon in one terminal and the dashboard in another:
harness daemon # terminal 1: the supervisor, in the foreground
harness # terminal 2: the dashboard (q to quit)
Where things live
| What | Default path |
|---|---|
| Config | ~/.config/harness/harness.toml (honors $XDG_CONFIG_HOME) |
| State, intent and run history | ~/.local/state/harness/state.json (honors $XDG_STATE_HOME) |
| Per-harness logs | ~/.local/state/harness/logs/NAME.log |
| Per-run logs of scheduled harnesses | ~/.local/state/harness/jobs/NAME/RUN_ID.log |
| Control socket (Linux) | $XDG_RUNTIME_DIR/harness.sock, usually /run/user/UID/harness.sock |
Control socket (macOS, no $XDG_RUNTIME_DIR) | ~/.local/state/harness/harness.sock |
The client finds the daemon through that socket, so both must agree on it. The service guide covers the one case where they don't.
Next: run the daemon as a service.