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:

keyrequiredmeaning
cmdyesthe program to run
argsnoarray of arguments; {workdir} and {name} are substituted
workdirnoworking directory (default $HOME); a good home for per-harness config the command reads
env_filenoa file sourced (into the environment) before launch
restart_delaynoseconds before the supervisor restarts a crashed/exited command (default 5)
tmux_socketnotmux -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

  • Supervisorharness@<name> (systemd) or harnessd.<name> (launchd) runs harness-run <name>, which starts the command in a detached tmux session (socket harness, session <name>) wrapped in a restart loop. A crash restarts the command inside the same session, so attach keeps working.
  • PATH — the systemd template adds ~/.local/bin to PATH (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