herdr-auto-resume
Fork notice: this is a fork of henryaj/autoclaude
(MIT, Copyright (c) 2025 Henry Stanley — see LICENSE, preserved unmodified).
This fork adds Herdr support behind a runtime abstraction. The original tmux TUI remains
available.

herdr-auto-resume monitors explicitly selected Herdr or tmux panes for Claude Code and Codex
usage limits, then schedules and verifies one provider-specific continuation when the limit
resets.
Install
Release tarballs are published on the GitHub Releases page:
https://github.com/Wave-Consulting-Netherlands/herdr-auto-resume/releases
Each archive contains the herdr-auto-resume binary. Replace the installed binary in place when
upgrading.
Install tagged source with Go:
go install github.com/Wave-Consulting-Netherlands/herdr-auto-resume@v0.7.0
Or build from a checkout:
git clone https://github.com/Wave-Consulting-Netherlands/herdr-auto-resume.git
cd herdr-auto-resume
go build -o ~/.local/bin/herdr-auto-resume .
Requirements: Go 1.23 or newer when building, Herdr 0.7.5 or compatible protocol 17 for the
Herdr runtime, and tmux only when using the tmux runtime or TUI.
Quickstart
Headless Herdr mode requires explicit pane selection:
herdr-auto-resume run --pane w1:p1
herdr-auto-resume doctor
herdr-auto-resume status
Use a second pane or state file for an independent watcher. The default Herdr state is under
the XDG state directory. A watcher holds .run; a second watcher on the
same state file fails fast. --state-file off disables persistence and the run lock.
Running it
Pane mode
The bare command starts the original tmux TUI. Use tab to toggle a pane, a/n to enable or
disable all panes, r to refresh, h/? for help, and q to quit.
For a headless pane watcher:
herdr-auto-resume run --pane w1:p1 --pane w2:p1 --interval 5s
herdr-auto-resume run --wait-for-panes --pane w1:p1
For the Herdr runtime, socket transport is now the built-in default when no --session is set
and no transport was explicitly requested. Use --transport cli as the opt-out. A
runtime.transport key in YAML is also explicit. When transport is not explicit, the helper
falls back to CLI with a warning naming the cause if runtime is tmux or --session is set.
Explicitly requesting an impossible combination still errors: socket with tmux, or socket with
--session. --session is not supported with socket transport.
systemd user service
mkdir -p ~/.config/systemd/user
cp packaging/systemd/herdr-auto-resume.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now herdr-auto-resume.service
On a headless host, enable lingering first. This is mandatory so the user service survives
logout:
loginctl enable-linger "$USER"
The unit uses the socket default plus --wait-for-panes, a user-service PATH,
restart-on-failure, and NoNewPrivileges=yes. Edit YAML and restart the service; do not edit
the unit for normal configuration changes.
launchd example
packaging/launchd/nl.wave-consulting.herdr-auto-resume.plist is example-only and untested.
Replace your-user and the binary/config paths, then load it with the normal per-user
launchctl bootstrap workflow. Its stdout and stderr are under ~/Library/Logs.
Operations
herdr-auto-resume status
herdr-auto-resume inspect <job-id-prefix>
herdr-auto-resume cancel <job-id-prefix>
herdr-auto-resume ack <job-id-prefix> [--reason "why"]
herdr-auto-resume revive <session-id-prefix>
herdr-auto-resume doctor
herdr-auto-resume doctor --transport socket --socket ~/.config/herdr/herdr.sock
herdr-auto-resume detect --provider claude --file path/to/pane-capture.txt
herdr-auto-resume detect --provider codex --file path/to/codex-capture.txt
ack releases a pane that a handled terminal job was still holding. Any terminal job that is
not RESUMED parks its pane: the watcher treats the pane as already owned and creates no further
jobs for it, permanently and — before v0.5.0 — invisibly. ack records acked_at and
acked_reason (default acknowledged by operator) in one locked transaction, after which a
NEW limit episode on that pane creates a job again; identical evidence stays suppressed, so
acknowledging cannot replay the episode you just handled. RESUMED jobs are rejected (nothing to
acknowledge) and active jobs are rejected in favour of cancel. status shows a PARKED column
naming the reason, and inspect reports parked and park_reason.
Codex workspace-credits and spend-cap banners park with an explicit billing reason ("Codex
billing action required: ...") because there is no reset to wait for — a human must add credits
or raise the cap. Such a pane is held until you ack it, which is deliberate: it is what makes
the state visible in status and acknowledgeable, rather than a silent no-op.
Job commands read configured state.file when --state-file is omitted. doctor reports version,
config, watcher-lock, Herdr, adapter, schema, and self-pane diagnostics. Run-lock errors name
the holder PID; use another --state-file for a second watcher.
revive resolves a unique Claude session-file prefix, refuses if any pane already carries that
session, takes a non-blocking per-session lease, records crash-recovery intent, and starts
claude --resume in a new Herdr workspace. It requires the Herdr runtime and a persistent
state file. It sends no continuation; once the pane is monitored, the normal detection and
verification path handles it.
Diagnosing a pane herdr will not tag
When a pane is not being picked up, or is picked up as the wrong agent, ask herdr what it
thinks before reading this tool's logs:
herdr agent explain --target <pane-id>
It explains herdr's own agent-detection state for that pane, which is the input everything here
depends on: admit_agent_events seeding, the agent_session correlation used by the
session-file channel, and the identity gate in the resume path all start from herdr's answer.
A pane herdr has not tagged will not be seeded, and its limit menu cannot be answered — the
menu answer deliberately requires a real agent_session.
Scrub HERDR_* from the environment when running herdr commands from inside a pane; children
inherit the live pane identity and will otherwise report on the wrong pane.
Configuration
The default file is ~/.config/herdr-auto-resume/config.yaml, or
$XDG_CONFIG_HOME/herdr-auto-resume/config.yaml. Use --config path for another file. The file
must contain version: 1; unknown keys are rejected.
Precedence is built-in defaults < config file < explicitly set flags. A flag set explicitly to
its built-in value still wins. If the default config is absent, behavior is byte-identical to
flag-only behavior. See packaging/config.example.yaml for the full commented schema.
version: 1
runtime:
type: herdr
herdr_bin: herdr
socket: ~/.config/herdr/herdr.sock
workspace: your-workspace
monitoring:
panes: [w1:p1]
interval: 3s
lines: 200
wait_for_panes: false
admit_session_matches: false
admit_agent_events: false
resume:
margin: 60s
max_wait: 192h
verify_timeout: 90s
answer_limit_menu: false
providers:
enabled: [claude, codex]
claude_prompt: continue
codex_prompt: Continue the previous task from where you stopped.
session_file_channel: false
state:
file: auto
Run flags: --config, --runtime, --transport, repeatable --pane, --interval, --lines,
--wait-for-panes, --dry-run, --test-pattern, --herdr-bin, --socket, --session, --workspace,
--state-file, --margin, --max-wait, --verify-timeout, --providers, --session-file-channel,
--admit-session-matches, --admit-agent-events, --answer-limit-menu, --claude-prompt, and
--codex-prompt.
Doctor flags: --config, --transport, --herdr-bin, --socket, --session, --workspace, and
--state-file. Job flags: --config and --state-file. Detect flags: --file and
--provider claude|codex.
v0.3.0 options
--wait-for-panes / monitoring.wait_for_panes: default false. At startup, retries
retryable reachability failures (connection refused, absent socket, timeout, or EOF) and zero
matching panes with a rate-limited, signal-aware loop. Permanent protocol, malformed-response,
permission/authentication, and configuration errors still fail fast.
--session-file-channel / providers.session_file_channel: default false. Reads Claude
session files for rate-limit observations and correlates them by agent_session; it requires
a persistent state file and is rejected with the tmux runtime.
--admit-session-matches / monitoring.admit_session_matches: default false. Per episode,
admits an otherwise unmonitored Herdr pane only when exactly one live pane's agent_session
matches the observation and the existing provider, cwd, self-pane, and validation gates pass.
It requires --session-file-channel, a persistent state file, and the Herdr runtime.
--answer-limit-menu / resume.answer_limit_menu: default false. For a manual Claude
limit menu, it is single-shot per episode and only answers when the literal question, the text
Stop and wait for limit to reset, and the cursor marker on that line are all present. It
never selects by option index, requires a persistent state file and the Herdr runtime, and is
subject to the read-then-send TOCTOU caveat because Herdr has no revision-conditional send.
v0.4.0 options
-
--admit-agent-events / monitoring.admit_agent_events: default false. Lets Herdr's own
agent detection define coverage instead of the static pane list: the watcher seeds from the
startup snapshot, follows pane.agent_detected for panes that appear later, and re-seeds
after a resync so a stream outage cannot silently narrow coverage. Socket transport only —
under --transport cli it logs one notice and stays inert.
Admission decides only whether a pane is looked at, never whether it may be typed into.
An event-admitted or seeded pane passes exactly the same resume gates as a configured one:
provider resolution, terminal identity, foreground process, working directory, menu/idle
safety, single-flight, and verification. The watcher's own pane and any explicitly disabled
pane are always refused, and every admission logs one line naming the pane, agent, and
trigger (startup-snapshot or pane.agent_detected).
v0.6.0 options
-
--transient-retry / monitoring.transient_retry: default false, and deliberately not
recommended yet. Classifies API-side stalls that are NOT reset-bearing usage limits — 429
without a reset, 5xx, overloaded, throttling, connection errors — as a separate transient
class and retries them 60s, 120s, 240s, then 300s, capped by
monitoring.transient_max_attempts (default 5) before parking. A reset-bearing limit always
wins over a transient. Single-flight and every resume gate still apply; each attempt logs the
pane, attempt number, and next delay.
The patterns are unverified. No real capture of these messages existed when the feature
was written, so the table in internal/detection/transient.go records the provenance of each
entry and is built to be replaced. scripts/limit-capture.sh now also captures
transient-looking panes so real fixtures can be collected. Leave this off until you have
captured a genuine transient and drilled it.
v0.8.0 behaviour
Two changes here have no config flag; they apply whenever answer_limit_menu is on and
whenever a resume is attempted.
-
Answering the limit menu now continues the work. Before v0.8.0 the menu path answered the
menu and then always parked MANUAL_REQUIRED, which unblocked the pane but abandoned whatever
it had been doing. Observed live on 2026-08-06: three real menu panes were answered, one
happened to self-recover because it had an in-flight turn, and two sat idle with their work
lost. The menu answer is still strictly single-shot, and the continuation is now a separate
decision made on a freshly-read pane: the menu must be gone, the pane must still resolve as
the same provider, and SafeToResume must pass. A pane that is busy after the answer is
parked menu answered; pane busy, resume suppressed — never queued, never retried.
The load-bearing guard is IsIdlePrompt. A pane blocked on an ordinary interactive question
(a multi-select prompt, not the limit menu) is not a limit menu and is not idle, so it
is refused. That case is pinned by TestMenuAnswerGoneInteractiveAskQuestionIsNotResumed
against a fixture captured from a real pane, because a naive idle check would type into the
question instead of resuming.
-
The pane content behind each action is captured. Every resume-time decision writes the
content that justified it to a captures/ directory beside the state file — the guarded
pre-answer read on the menu path, the SafeToResume read on the banner path. Files are 0600
in a 0700 directory, individually truncated at 256 KiB and evicted oldest-first past 4 MiB
total. Any capture failure is logged and ignored: a capture can never block, delay, or fail a
resume. This exists because the limit menu is redrawn in place and never enters scrollback, so
after 2026-08-06 the real menu text could not be recovered from any pane — the fixtures this
project still needs can only be taken at the instant the watcher acts.
Note that these files contain verbatim terminal output, including anything a session had
on screen. They carry the same protection as the state file, but if a pane displays a secret,
that secret is now also on disk until eviction. Delete the directory to opt out retroactively.
These session-identity features remain opt-in and are not enabled by the shipped service
examples. The YAML keys must use the exact nesting shown above; unknown keys are rejected.
Upgrade
Download the new release, replace ~/.local/bin/herdr-auto-resume, then restart the pane watcher
or user service. State files remain schema-compatible. In addition to the store's
.run single-watcher lease and .lock transaction lock, the session-file channel
uses .scan.json for cursors/pending state and .scan.lock for its exclusive
sidecar lock. revive also uses .revive..lock for its per-session lease.
Semantic versioning applies: config-schema changes are minor releases and fixes are patch
releases. The v0.3.0 release flips the Herdr default to socket after the production soak and
aged-connection drill. Phase 7 validation targets Herdr 0.7.5/protocol
17; release notes record the Claude Code and Codex versions used for each live acceptance run
(Codex 0.144/0.146 were covered in Phase 5 validation).
Troubleshooting
Start with herdr-auto-resume doctor. If a run says the state file is already in use, the named
PID owns the watcher lock. Confirm it, stop it if necessary, or choose another state file. Do
not delete .run while a watcher may still be running. A missing default config is normal and
is reported as INFO config: none; an explicitly requested missing or invalid config is an error.
For socket problems, run doctor --transport socket --socket ... and verify Herdr protocol 17.
For a service that restarts, inspect journalctl --user -u herdr-auto-resume.service and check
the binary path, PATH, pane IDs, and loginctl show-user "$USER" -p Linger.
How it works
When enabled, the coordinator uses two detection channels: Claude session files are authoritative
where a durable record exists, while screen scraping remains the fallback for visible pane evidence.
Both channels resolve the same provider/session/reset episode identity, so delayed or duplicate
evidence does not create duplicate jobs. A limited pane that yields no job logs one diagnostic
line per evidence hash naming the reason. The scheduler validates provider/process/session
identity, sends the provider-specific continuation, and verifies cleared evidence or changed
output. State remains JSON schema 1; the store and scan sidecars serialize their own short
transactions.
Development
export PATH=$HOME/.local/go/bin:$PATH
export GOCACHE=/tmp/herdr-go-cache
export GOMODCACHE=/tmp/herdr-go-modcache
go build ./...
go vet ./...
go test ./... -race -count=1
License
MIT License — see LICENSE. Upstream attribution is preserved unmodified.
Credits
Forked from Henry Stanley's autoclaude and built with Herdr, Claude Code, and Codex.