zsh-harnessd
July 18, 2026 · View on GitHub
systemctl for harnesses. A small Oh My Zsh / zsh plugin that keeps
long-running commands — agent CLIs, REPLs, watchers, anything — alive in
detached tmux sessions, supervised by systemd (--user) on Linux or
launchd on macOS, with one consistent set of verbs on both.
It's deliberately generic: a harness is just cmd + args + workdir + an
optional env_file. harnessd knows nothing about what you run inside it.
harnessd list # every harness + up/down
harnessd create <name> # scaffold a config and open $EDITOR
harnessd start <name> # supervise + launch (enable on boot)
harnessd stop <name>
harnessd restart <name>
harnessd status [<name>]
harnessd log <name> # follow the supervisor log
harnessd attach <name> # attach the live tmux session (^b d detaches)
harnessd edit <name>
Why
You end up hand-rolling the same thing every time you want an unattended CLI
that survives crashes and logout and is still watchable: a systemd unit, a tmux
session, a restart loop, a set of start/stop/restart/log/attach wrappers.
harnessd is that pattern, factored out once.
Install
Oh My Zsh (custom plugin):
git clone https://github.com/stump-wtf/zsh-harnessd \
"${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/plugins/zsh-harnessd"
# then add zsh-harnessd to plugins=(…) in ~/.zshrc
Plain zsh:
git clone https://github.com/stump-wtf/zsh-harnessd ~/.zsh/zsh-harnessd
echo 'source ~/.zsh/zsh-harnessd/zsh-harnessd.plugin.zsh' >> ~/.zshrc
On first use the plugin symlinks harness-run into ~/.local/bin and (Linux)
installs the harness@.service systemd template into ~/.config/systemd/user/.
Requirements: zsh, tmux, and either systemd --user (Linux) or launchd
(macOS). To keep Linux harnesses running while you're logged out:
sudo loginctl enable-linger "$USER".
Harness config
Harnesses live in ~/.config/harnessd/harnessd.toml (override the dir with
$HARNESSD_DIR), one [<name>] table each:
| key | required | meaning |
|---|---|---|
cmd | yes | the program to run |
args | no | array of arguments; {workdir} and {name} are substituted |
workdir | no | working directory (default $HOME); a good home for per-harness config the command reads |
env_file | no | a file sourced (into the environment) before launch |
restart_delay | no | seconds before the supervisor restarts a crashed/exited command (default 5) |
tmux_socket | no | tmux -L socket (default harness) |
~ and $HOME are expanded in every value. Parsing needs python3 (>= 3.11,
for stdlib tomllib).
# ~/.config/harnessd/harnessd.toml
[crush-signal-channel]
cmd = "crush"
args = ["--yolo", "--data-dir", "{workdir}", "--channels", "server:signal"]
workdir = "~/.local/share/crush-signal-channel"
env_file = "~/.config/vault/secrets-static.env"
harnessd start crush-signal-channel
harnessd attach crush-signal-channel # watch it; ^b d to leave it running
harnessd log crush-signal-channel
Harnessing an agent CLI that needs a login
Some agent CLIs (e.g. Claude Code) need a one-time interactive login that
persists in the user's config dir. Do it once on first boot (claude /
claude auth login); harnessd only supervises the process, so no credentials
live in the harness config. For an interactive assistant you drive remotely
rather than a chat bot, a harness is just cmd = "claude" with
args = ["--remote-control", "--dangerously-skip-permissions"].
How it works
- Supervisor —
harness@<name>(systemd) orharnessd.<name>(launchd) runsharness-run <name>, which starts the command in a detached tmux session (socketharness, session<name>) wrapped in a restart loop. A crash restarts the command inside the same session, soattachkeeps working. - PATH — the systemd template adds
~/.local/bintoPATH(user services otherwise get a minimal one), so tools your command shells out to are found. - Idempotent — re-running a start (boot, redeploy) never double-launches.
License
MIT © Joe Stump