Skip to main content

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

WhatDefault 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.