agent-resumer
v0.6.0
Published
Local auto-resume supervisor for AI coding-agent terminals: detect Claude Code, Codex CLI, OMX, OpenCode, and managed-command limits, wake the original tmux pane, and verify recovery.
Maintainers
Readme
agent-resumer
agent-resumer is a local auto-resume supervisor for AI coding-agent terminals. It watches Claude Code, Codex CLI, OMX, and OpenCode local session state, detects usage-limit/rate-limit reset signals, waits until the provider reset/backoff window, and resumes the original terminal pane when it can.
agent-resumer is not a rate-limit bypass. It only retries after a provider-declared reset time, a retry-after window, or a bounded fallback backoff.
What changed in the current UX
The preferred path is now tmux same-pane resume:
agent-resumerruns as a background watcher.- Shell shims let you keep typing normal commands such as
claude,codex, andopencode. - If those commands run inside tmux, the shim records the current
$TMUX_PANE. watchlinks the transcript session to that pane.- When the reset time arrives, agent-resumer sends
continueto the original pane withtmux send-keysand flashes a confirmation banner on that pane so you can see it happened. - If no safe visible pane is known, it does not silently start a background agent; use
--headlessoragent-resumer resumewhen you explicitly want that.
This means the original Claude/Codex/OpenCode screen can continue instead of a hidden detached process doing work elsewhere.
Confirming a resume happened:
- Every pane resume flashes a
tmux display-messagebanner on the resumed pane (agent-resumer ▸ resumed <id>). Disable it with--no-pane-bannerorAGENT_RESUMER_PANE_BANNER=0. - Set
focusOnResume(config),--focus-on-resume, orAGENT_RESUMER_FOCUS_ON_RESUME=1to also switch your tmux view to the resumed pane. Off by default so a resume never steals focus from what you are doing. - Notifications (
--notify) name where the resume landed —resumed in pane %Norresumed headless (background) — log: <path>— so a background resume is never indistinguishable from a visible one.
If several tmux/purplemux panes share the same working directory, agent-resumer stays conservative. A watch tick reports safeBlocked: true, candidateCount, candidateDetails, a diagnoseCommand, and one bindCommands entry per candidate pane instead of sending continue to a guessed pane. Bind the correct pane once, or restart the agent through the shim so future sessions are recorded automatically.
Additional pane safety guards:
- Shims now remember the tmux socket/server from
$TMUX; watch/diagnose/resume reuse that socket instead of assuming the default tmux server. - Background services read
AGENT_RESUMER_TMUX_SOCKETS(comma/newline-separated socket paths). The installed launchd wrapper populates it from standard tmux socket directories plus one-hop$HOME/.*/tmux.socksockets, so tmux-compatible wrappers work without product-specific socket paths in agent-resumer itself. - Weak stored pane bindings expire after
paneBindingTtl(config file, seconds),--pane-binding-ttl <ms>, orAGENT_RESUMER_PANE_BINDING_TTL_MSunless the pane still has strong agent evidence. --strict-pane-echoorAGENT_RESUMER_STRICT_PANE_ECHO=1refuses to send pane input whencapture-panecannot verify the prompt.- A pane-send failure (including a pane that vanished mid-send) is reported as
resume_failedand retried — it is never silently turned into a background/headless resume. Explicit headless stays--headless/resume. - cwd matching uses realpath when possible, so symlinked worktrees match their real tmux cwd.
agent-resumer doctor --jsonchecks shim PATH order, warns about shell functions that call absolute agent binaries and bypass shims, and reports whethertmuxis installed (when absent it notes that same-pane resume is disabled and points to explicit--headless/resumeusage plus how to install tmux).
Fastest safe path
For the normal product experience, let agent-resumer manage tmux for you:
npm install -g agent-resumer
agent-resumer install-shims --force --auto-tmux
agent-resumer service install
agent-resumer doctor --json
exec $SHELL -lAfter that, run your agent normally:
claude
# or
codex
# or
omx
# or
opencodeIf you are already inside tmux, the shim records the current $TMUX_PANE when available and never opens a nested tmux session just because --auto-tmux is enabled. Wrapper environments that expose $TMUX but not $TMUX_PANE still run the real CLI directly; they simply skip pane registration until a pane id is available. If you are outside tmux, the opt-in --auto-tmux shim opens a managed tmux session, runs the agent inside it, attaches you there, and lets the normal pane registry capture the new pane. You do not have to start tmux manually.
For a read-only dry run with no shims/service:
npm install -g agent-resumer
agent-resumer --version
agent-resumer watch --once --dry-run --jsonThe advanced path is more complex because it intentionally tracks which exact tmux/purplemux pane belongs to each agent session. The moving parts are:
- shell shim PATH order (
claude,codex,omx,opencodeshould resolve through~/.agent-resumer/shimsfirst), - existing shell functions or wrappers that may bypass PATH by calling absolute binaries,
- tmux socket/server identity, especially with cmux/purplemux or multiple tmux servers,
- pane binding confidence when several panes share the same cwd,
- daemon state in
~/.agent-resumer/state.json, locks, and resume verification windows.
The complexity buys safety: if the right pane is ambiguous, agent-resumer reports candidates and refuses to guess instead of typing continue into the wrong terminal.
Windows and WSL
WSL is the recommended Windows story today. If Claude/Codex/OMX/OpenCode, tmux, and the transcripts all live inside the same WSL distro, agent-resumer behaves like it does on Linux. install-shims --auto-tmux can create and attach managed tmux sessions inside that distro.
What is not the same thing as WSL support: native PowerShell/CMD sessions, Windows GUI app terminals, or ConPTY-first desktop integrations. Those can still use agent-resumer run <engine> -- <command...> experimentally, but pane-aware same-terminal recovery is designed around tmux-compatible environments.
Comparison with nearby OSS
| Tool | Main model | Strong at | agent-resumer difference |
| --- | --- | --- | --- |
| dennis-lynch/claude-code-auto-resume | Claude Code hook sleeps until reset then returns {"continue": true} | Simple Claude-only hook | agent-resumer watches multiple engines and verifies recovery after a resume action. |
| FusionCube18712/claude-codex-auto-resume | PTY passthrough wrapper for Claude/Codex/Gemini/generic commands | Cross-platform wrapper UX, Go binaries | agent-resumer does not require every session to start inside a wrapper; it can map existing local transcripts back to safe tmux panes. |
| agent-auto-resume (aar) | daemon + PTY managed commands | Claude/Codex/Antigravity managed-command flow | agent-resumer is stricter about original-pane binding, same-cwd ambiguity, and false recovery claims. |
| primeline-ai/claude-tmux-orchestration | tmux worker orchestration | Parallel Claude worker management | agent-resumer is not an orchestrator; it focuses on rate/usage-limit recovery for existing local sessions. |
| ccs / Claude session registries | name/list/resume Claude sessions | Human-friendly Claude session lookup | agent-resumer adds reset parsing, scheduling, pane resume, fallback resume, and verification. |
Why not just use a wrapper?
Wrappers are great when every agent process starts through that wrapper. agent-resumer is optimized for the messier local reality:
- you already have long-running Claude/Codex/OMX/OpenCode sessions,
- a shell function or cmux/purplemux pane may be involved,
- multiple panes may share the same cwd,
- a resume command can launch but still be rate-limited,
- sending input to the wrong pane is worse than doing nothing.
So agent-resumer favors fail-closed supervision over eager retries. resume_sent is not treated as recovered until there is evidence.
Demo
Run a local no-network demo that parses a fake Claude reset, writes a temporary state file, and prints the scheduler decision and isolated state:
scripts/demo-local.shTo record it as a terminal demo:
asciinema rec /tmp/agent-resumer-demo.cast --command './scripts/demo-local.sh'
# optional if you have agg installed:
# agg /tmp/agent-resumer-demo.cast docs/demo.gifExperimental managed-command engines
agent-resumer run <engine> -- <command...> can now be used with engines that do not have a transcript watcher yet, including generic, gemini, and antigravity/agy style CLIs:
agent-resumer run gemini -- gemini "continue the task"
agent-resumer run antigravity -- agy run
agent-resumer run generic -- my-agent-cli --workFor these engines, agent-resumer parses common reset signals such as RESOURCE_EXHAUSTED, rate_limit_error, quota exceeded, and try again in .... After the reset window, it replays the original command in the original cwd. This is intentionally labeled experimental: it is reset-aware command replay, not a provider-specific session-ID resume contract.
Supported agents
| Agent | Observed local state | Preferred resume | Fallback resume |
| --- | --- | --- | --- |
| Claude Code | ~/.claude/projects/<project>/<session-id>.jsonl | tmux send-keys <pane> continue Enter | claude -p --resume <session-id> --output-format json <prompt> |
| Codex CLI | ~/.codex/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl | tmux send-keys <pane> continue Enter | codex exec resume <uuid> --json <prompt> |
| OMX | Codex-shaped local session files | tmux send-keys <pane> continue Enter | codex exec resume <uuid> --json <prompt> |
| OpenCode | ~/.local/share/opencode/opencode.db | tmux send-keys <pane> continue Enter | opencode run -s <session-id> --dir <cwd> --format json <prompt> |
| Managed command (generic, gemini, antigravity) | agent-resumer run <engine> -- <command...> state | command replay in original cwd | command replay in original cwd |
OpenCode discovery uses the sqlite3 command. If sqlite3 is unavailable, OpenCode sessions are skipped.
Install
Recommended install
curl -fsSL https://raw.githubusercontent.com/voidmatcha/agent-resumer/main/scripts/install.sh | bashThe installer:
- installs the npm package globally
- installs
tmuxwith Homebrew when available and missing - installs shell shims for
claude,codex,omx, andopencode - adds a dedupe-safe
~/.agent-resumer/shimsblock to your shell profile - installs the background service
Restart your shell after install so the managed PATH block can remove duplicate shim entries and put one shim path first.
Then keep using the normal commands:
claude
codex
opencodeCLI only
This installs only the CLI. It does not start a background service and does not install pane-aware shims unless you run install-shims yourself.
npm install -g agent-resumer
agent-resumer --version
agent-resumer --helpBackground service
Use the service when agent-resumer should keep watching after the terminal closes.
agent-resumer service install
agent-resumer service statusnpm-only install
npm install -g agent-resumer
agent-resumer --version
agent-resumer install-shims --force --auto-tmux
agent-resumer service installBy default, install-shims adds a managed PATH block for ~/.agent-resumer/shims to your shell profile. On shell startup, that block removes duplicate agent-resumer shim entries and puts one shim path first. Use --no-profile if you only want the shim files. If you skip install-shims, agent-resumer can still detect limits, but background resumes require explicit --headless/resume and it will not reliably know which tmux pane to wake.
Existing shell functions and aliases
Existing shell functions can still work. For example, this kind of wrapper is compatible:
claude () {
local override
override="$(serena prompts print-cc-system-prompt-override 2>/dev/null)"
if [ -n "$override" ]; then
command claude --system-prompt="$override" "$@"
else
command claude "$@"
fi
}Why: command claude bypasses the shell function but still resolves claude through PATH. If the agent-resumer shim directory is first in PATH, that function calls the shim, and the shim forwards all arguments to the real Claude binary after recording $TMUX_PANE.
Wrappers that call an absolute binary path, such as /opt/homebrew/bin/claude, bypass PATH shims. Change those wrappers to command claude ... or manually register/bind the pane.
Check resolution with:
type -a claude
whence -p claude # zsh
command -v claudeCommands
agent-resumer watch --once --json
agent-resumer watch --watch --interval 30000 --quiet
agent-resumer status --json
agent-resumer diagnose claude:<session-id> --json
agent-resumer resume claude:<session-id> --json
agent-resumer bind claude:<session-id> --pane %42 --json
agent-resumer install-shims --force --auto-tmux
agent-resumer service statusUseful flags:
--headlessskips tmux pane resume and explicitly uses the headless path.--pane-prompt <text>changes the text sent to interactive panes; default:continue.--prompt <text>changes the headless resume prompt.--quietsuppresses routine no-op loop logs.--jsonprints machine-readable output.
Pane binding model
agent-resumer stores pane metadata in ~/.agent-resumer/state.json:
{
"panes": {
"%42": {
"engine": "claude",
"cwd": "/project",
"source": "shim"
}
},
"sessions": {
"claude:abc": {
"engine": "claude",
"sessionId": "abc",
"cwd": "/project",
"pane": "%42",
"paneSource": "shim"
}
}
}Binding confidence, from strongest to weakest:
- tmux-title-session — multiple same-cwd panes can be disambiguated by matching the tmux pane title against recent transcript/user-task text.
- tmux-capture — watcher found a same-cwd pane whose captured screen content looks like the target agent TUI (Claude/Codex/OMX/OpenCode). This can repair a stale or wrong manual binding and reports
correctedFrom. - shim — command started through the installed shim while
$TMUX_PANEwas set. - manual — you explicitly ran
agent-resumer bind <session> --pane <pane>; capture evidence can still override it if it clearly points to a different same-cwd agent pane. - tmux-passive — watcher found exactly one matching tmux pane by engine command and cwd.
- tmux-title — watcher found exactly one wrapped agent pane where cwd matches and
pane_titleexactly equals the cwd basename (for tmux/purplemux panes whose current command appears asnodeor a version string). - headless fallback — no safe pane was known.
If passive detection finds multiple equal-confidence candidate panes, agent-resumer does not guess. It reports an ambiguous pane action and, for same-cwd ambiguity, skips headless fallback by default so a detached resume does not leave the visible pane unchanged. For already-running pre-shim sessions, watch captures only same-cwd candidate panes, caches captures for the tick, and prefers panes whose visible content matches the target agent.
Pane sends use a handshake: agent-resumer pastes the prompt, verifies that capture-pane can see it, then presses Enter. If the pane does not echo the input, the action fails as pane_input_not_visible rather than reporting a false launch.
How it works
observe local state → map tmux pane → classify limit → schedule reset → send continue to pane → fallback headless → verify recovery- Observe — scans active Claude/Codex JSONL transcripts and OpenCode SQLite rows.
- Map pane — uses shim registrations, manual binds, or safe passive tmux detection.
- Classify — detects structured usage-limit/rate-limit signals.
- Schedule — stores
resumeAtusing provider reset time plus buffer or fallback backoff. - Resume — sends
continueto the original tmux pane first; if unavailable, launches the headless resume command. - Verify — marks
resume_sentafter a pane/headless launch and only marksrunning/recoveredafter later transcript activity proves recovery.
Claude statusLine reset timestamps
Claude Code statusLine JSON can expose machine-readable subscription windows:
rate_limits.five_hour.resets_atrate_limits.seven_day.resets_at
Configure Claude Code to call:
{"statusLine":{"type":"command","command":"agent-resumer statusline --quiet"}}When a later Claude transcript says a limit was hit but omits a reset time, agent-resumer can use the saved statusLine reset timestamp. Monthly spend caps (/usage-credits) are kept separate because they are user-configured billing limits and may not expose a reliable reset timestamp.
Monthly spend caps
Claude monthly spend-cap messages such as:
You've hit your monthly spend limit.
/usage-credits to adjust your monthly spend limit.are probeable but not exactly schedulable unless Claude exposes a reset time or the user changes the cap. agent-resumer keeps them in a retryable state and will try the same-pane resume path when due, but it will not claim recovery until transcript activity proves the session resumed.
Service
agent-resumer service install
agent-resumer service start
agent-resumer service stop
agent-resumer service status
agent-resumer service uninstall- macOS: user
launchdservicecom.voidmatcha.agent-resumer - Linux/WSL2: user
systemdtimeragent-resumer.timer
State and logs
- State:
~/.agent-resumer/state.json - Detached resume logs:
~/.agent-resumer/resume-logs/ - Config:
~/.agent-resumer/config.json
Existing tmux / purplemux panes
install-shims is still the most reliable path because it records the exact $TMUX_PANE when claude, codex, or opencode starts. watch also has a recovery path for sessions that were already running before the shim was installed: it lists live tmux panes, maps each pane cwd to Claude's ~/.claude/projects/<encoded-cwd>/ transcripts, scans matching transcript tails for limit signals, and then uses transcript title / last-prompt hints to choose the correct pane.
Useful checks:
agent-resumer watch --once --json
agent-resumer diag <claude-session-uuid> --json
agent-resumer diagnose --json # summarize every tracked session when you do not know the idIf several panes share the same cwd and no transcript title or prompt hint identifies the right pane, agent-resumer stays conservative instead of guessing. In that case, bind once with agent-resumer bind claude:<uuid> --pane %12 or restart the agent through the shim.
tmux can recycle pane ids after panes are closed. If a previously bound pane was reused for a different agent, run agent-resumer diag <uuid> --json and re-bind to the currently visible pane before relying on the watcher.
Safety notes
- agent-resumer only sends the configured pane prompt, default
continue, after the scheduler says the session is due. - It avoids passive pane guesses when multiple panes match, and in multi-pane same-cwd workspaces it only sends to tmux when the pane has strong session/title evidence or a single exact engine command match.
- It does not bypass provider limits or modify provider accounts.
- Use
--headlessif you explicitly want background/headless behavior for a command.
License
Apache-2.0
