herdr-auto-resume

command module
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 6, 2026 License: MIT Imports: 36 Imported by: 0

README

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.

CI

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.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal
config
Package config loads the small, strict YAML configuration surface.
Package config loads the small, strict YAML configuration surface.
jobs
Package jobs owns persistent resume-job lifecycle and safety gates.
Package jobs owns persistent resume-job lifecycle and safety gates.
provider
Package provider contains provider-neutral detection and resume contracts.
Package provider contains provider-neutral detection and resume contracts.
provider/claude
Package claude adapts the established Claude detection behavior to the provider contract without moving or changing the detection implementation.
Package claude adapts the established Claude detection behavior to the provider contract without moving or changing the detection implementation.
provider/codex
Package codex detects and resumes the Codex CLI's live terminal UI.
Package codex detects and resumes the Codex CLI's live terminal UI.
revive
Package revive implements the one-shot Claude session revive operator.
Package revive implements the one-shot Claude session revive operator.
sessionfile
Package sessionfile scans Claude Code's durable session records for rate limit observations.
Package sessionfile scans Claude Code's durable session records for rate limit observations.
store
Package store persists scheduled resume jobs.
Package store persists scheduled resume jobs.
terminal
Package terminal contains small, dependency-free terminal text helpers.
Package terminal contains small, dependency-free terminal text helpers.
tui

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL