Three Things That Broke While Setting Up herdr

herdrzshterminalClaude Code

herdr is a terminal workspace manager for running several coding agents at once. I’d had the binary sitting on disk unused and finally wired it up properly.

Installation is a handful of commands. Three things still stopped me, and none of them announced what was wrong.

1. compinit -C silently ignores new completions

Standard zsh completion setup:

herdr completion zsh > ~/.zsh/completions/_herdr

Then in .zshrc:

fpath=("$HOME/.zsh/completions" $fpath)
autoload -Uz compinit && compinit -C

-C skips the cache-validity check to speed up shell startup. Plenty of “make your zsh fast” posts recommend it. It also means zsh never scans for completion files it hasn’t already seen.

The file is there. Nothing errors. Tab completion just does nothing.

To check whether a completion actually registered:

echo ${_comps[herdr]}

With -C, that’s empty. Drop the flag, open a new shell, and you get _herdr.

Don’t use -C in the same change where you add a new directory to fpath. Once the cache has been rebuilt you can put it back.

An accidental discovery while fixing this: my .zshrc had no compinit call at all, meaning the zsh completion system had never been initialised. Adding it brought back completions for a bunch of other tools I’d assumed just didn’t ship any.

2. The TUI needs a TTY. The server doesn’t.

Running herdr opens a TUI, which requires a TTY. That rules it out of scripts, CI, and any background context — which looked like a wall for automating the setup.

The client and server are separable:

nohup herdr server > /dev/null 2>&1 &

herdr server is headless and starts without a TTY. Bring the server up however you like; a human attaches later with herdr.

To confirm it actually detached, check the parent PID:

ps -o pid,ppid,command -p <PID>

PPID 1 means it’s been reparented to init and will outlive the shell that started it.

Where things live:

~/.config/herdr/herdr.sock         srw------- (owner only)
~/.config/herdr/herdr-server.log
~/.config/herdr/session.json       persisted workspace state

Stop it with herdr server stop.

The general lesson: before concluding an interactive tool can’t be automated, check whether it has a daemon mode hiding behind the interface.

3. The workspace ID isn’t in the id field

herdr’s control commands return JSON. I created a workspace, parsed the response for its ID, and kept getting nonsense downstream.

herdr workspace list | python3 -m json.tool

The identifier is workspace_id. The id field in the same payload is a request echo, containing things like cli:workspace:list.

workspace_id : w1                    ← the actual ID
id           : cli:workspace:list    ← echo of your request

The docs say to read IDs from responses rather than constructing them, which is good advice that quietly assumes you’re reading the right key. id is a common enough name that you don’t stop to check.

The model, once it clicked

Concept Example ID What it is
workspace w1 Top level — one context
tab w1:t1 A tab inside a workspace
pane w1:p1 An actual terminal — shell, tests, server
agent A coding agent detected inside a pane

IDs are opaque and stable, and closed ones are never reused.

One behaviour worth knowing: agent start requires an existing available shell pane. It won’t create or split a layout for you. Get the pane first.

Agent lifecycle states:

Distinguishing blocked from done is the whole point when several agents are running. You stop hunting through windows for the one that’s waiting on you.

Checking an integration before installing it

The Claude Code integration is one command:

herdr integration install claude

It edits ~/.claude/settings.json to add a SessionStart entry. I already had several hooks there, so I checked whether they survived. They did.

The installed hook also exits immediately unless HERDR_ENV=1, so running Claude Code outside herdr is unaffected. Removal is herdr integration uninstall claude.

Those are the two things worth verifying whenever a tool offers to modify your shell config or editor settings: does it preserve what’s already there, and is there a clean way out. Back up first regardless:

cp ~/.zshrc ~/.zshrc.bak.$(date +%Y%m%d)

Takeaways

All three failures were of the “the config is right, so why isn’t it working” kind, which is the slowest kind to diagnose.

← All posts