Profile
Back to NewsBack
GitHub Trending 35 min
Reader Mode
cheapestinference/claude-auto-retry: Auto-retry Claude Code on subscription rate limits, API overload (529/5xx) and safeguard false positives — waits for the printed reset, exponential backoff, tmux-based

cheapestinference/claude-auto-retry: Auto-retry Claude Code on subscription rate limits, API overload (529/5xx) and safeguard false positives — waits for the printed reset, exponential backoff, tmux-based

11 hours ago

claude-auto-retry

Automatically retry Claude Code sessions when you hit Anthropic subscription rate limits.

When Claude Code shows "5-hour limit reached - resets 3pm", this tool waits for the reset and sends "continue" automatically. You come back to find your work done.

No dependencies. No workflow change. Just install and forget.

npm version</a> License: MIT</a> Node.js >= 18</a>

📢 Status (Oct 2026): Claude Code now does the core of this natively. Since v2.1.234 the CLI waits in the open session and continues the task on its own when a claude.ai usage limit resets — on by default in interactive subscription sessions (official docs; #13354 and #35744 closed as fixed). If all you ever needed was "wait out the 5-hour limit on the laptop in front of you", you no longer need this tool for that.
> What this tool still covers — verified against the docs and the v2.1.310 binary: keeping the session and the wait alive when your SSH connection drops or the terminal closes (the native wait dies with the process and does not restart on /resume), waits the native feature won't start on its own (weekly resets more than 24h out, model-specific limits, Remote Control sessions), and the failure modes it doesn't touch at all: terminal API overloads, safeguard false-positives, interrupted streams, and the near-limit wrap-up parking. Details in Native auto-continue vs this tool.

💡 Why wait out the limit at all? This tool auto-resumes Claude Code the moment you're rate-limited — but if you run overnight jobs or always-on agents, there's a way to stop hitting the wall in the first place. See how it's done →

The Problem

You're in the middle of a long task with Claude Code. After a while, you see:

Usage limit reached · limit resets 3pm

On Claude Code older than v2.1.234, that's the end of it: Claude stops, and hours later you have to come back and type "continue" yourself.

On v2.1.234 and later, the CLI itself waits and continues (details below) — if the session survives the wait, and if the wait armed at all. The problem that's left is everything around that happy path:

  • The wait dies with the process. SSH drops, the terminal closes, you log out — the session is gone, and the wait does not restart on /resume. An overnight run on a remote box lives or dies on the connection staying up.
  • Lots of waits never arm. Weekly limits resetting more than 24 h out, Opus/Sonnet-specific limits while you run another model, Remote Control sessions, and every headless claude -p run.
  • Limits aren't the only way a session parks. A terminal API overload, a safeguard false-positive, a response truncated by a laptop suspend, or the ~95% wrap-up notice all leave the session sitting at an idle prompt — and nothing native resumes any of them.
If you run long tasks overnight, over SSH, or unattended, any of these still kills your productivity.

Native auto-continue (v2.1.234+) vs this tool

Claude Code v2.1.234 (October 2026) shipped native auto-continue: when a claude.ai usage limit stops Claude mid-task in an interactive session, the CLI shows Continuing automatically at 3:45pm · esc to cancel, waits, and prompts Claude to pick the task back up. It is on by default for claude.ai subscription logins (toggle: Continue automatically at usage limit in /config).

For the core case, the native feature is better than any terminal scraper, including this one:

  • The reset time comes structurally from the API (an epoch timestamp), not parsed out of banner text — no timezone or format pitfalls.
  • It continues early if usage becomes available before the reset (you add usage credits, re-login after /upgrade, or switch models during the wait).
  • If the continued task hits the limit again, it re-arms itself, up to twice in a row.
If you work interactively, on one machine that stays awake, signed in with a claude.ai subscription: the native feature covers you, and this tool's usage-limit path is redundant.

Where the native feature stops — each row verified against the official docs and the v2.1.310 binary:

| Scenario | Native auto-continue | claude-auto-retry | |----------|----------------------|-------------------| | 5-hour/session limit, interactive subscription session | ✅ default on | Redundant — the monitor stands down when the session resumes | | SSH drops, terminal closes, you exit during the wait | ❌ the wait dies with the session and does not restart on /resume, relaunch, account switch, or a Desktop/cloud handoff — the docs say "keep the session open" | ✅ the tmux wrapper is the "keep the session open" machine: claude and the wait survive the disconnect; reattach to find it working | | Machine slept >~30 min past the reset | ⚠️ goes stale: shows Press enter to continue and waits for a human | ⚠️ the monitor sleeps with the box too — moot on the always-on machines tmux is for | | Weekly limit resetting >24 h out | ⚠️ won't arm on its own (manual: /rate-limit-options → Wait here, then continue automatically) | ✅ parses dated weekly resets (resets Oct 9, 10am) and waits | | Opus/Sonnet-specific limit while running another model | ⚠️ won't arm on its own | ✅ waits on the printed reset | | Remote Control / agent-team teammate session | ⚠️ won't arm on its own | ✅ the monitor doesn't care who's attached | | Headless claude -p / background sessions | ❌ not offered at all | ✅ --print mode: buffers, waits, re-executes | | API key / Bedrock / Vertex / usage-based billing | ❌ not offered (no reset to wait for) | Usage path n/a for the same reason — but the overload backoff still applies | | Terminal API overload (529/5xx after Claude Code's internal retries give up) | ❌ | ✅ exponential backoff + StopFailure hook | | Safeguard/AUP false positive | ❌ | ✅ bounded re-send | | Response truncated by suspend / dropped connection | ❌ | ✅ one resume | | Turn parked by the ~95% wrap-up notice | ❌ (it's Claude Code's own wind-down) | ✅ one nudge | | Limit hit repeatedly after resuming | Stops after 2 consecutive re-arms | maxRetries (default 5), configurable |

One config gotcha worth knowing: the native setting (autoContinueAtUsageLimit) is only honored from user or managed settings — a project-level settings file doesn't arm it.

Running both is safe by design. On v2.1.234+ the native wait and this monitor arm in parallel (the native wait's own Usage limit reached · limit resets 3:45pm render parses as a normal banner here). Before ever typing, the monitor re-checks that the limit banner is still present and the session hasn't resumed; once the native continue fires, the monitor logs user-continued and goes back to watching. When the native Continuing automatically at … · esc to cancel line is on screen, the monitor defers rather than races: it adds nativeDeferSeconds (180 s) to its own reset + margin wait, holds off typing for up to 3 minutes while native shows Continuing shortly (then sends as the backstop, with a warning), and re-arms instead of typing when native has armed a fresh wait for a later reset — so native's continue goes first and ours is only the backstop. The re-arm is bounded: the banner's reset must differ from the one the wait was armed for (a static or quoted pair that reads the same reset every time sends instead, so maxRetries still applies), and at most 3 re-arms run back to back. Confirming the menu's Wait here, then continue automatically row also adds the defer, since that confirm is what arms native. Without that line visible, the worst race is both submitting one "continue" — Claude Code treats the extra one as a prompt you sent. The two are complementary rather than rivals: the tmux wrapper is what lets the native wait survive a disconnect, and the monitor picks up everything the native feature doesn't arm.

The Solution

npm i -g claude-auto-retry
claude-auto-retry install

That's it. Type claude as you always do. When the rate limit hits, the tool:

  1. Detects the rate limit message in the terminal
  2. Parses the reset time (timezone-aware)
  3. Waits until the limit resets + 60s margin
  4. Verifies Claude is still the foreground process
  5. Sends "continue" automatically
You come back to find your task completed.

How it Works

You type "claude"
       │
       ▼
  Shell function (injected in .bashrc/.zshrc)
       │
       ├─ Already in tmux? ──▶ Start background monitor
       │                        Launch claude with full TUI
       │
       └─ Not in tmux? ──▶ Create tmux session transparently
                             Launch claude + monitor inside
                             Attach (looks the same to you)

MONITOR (background, ~0% CPU): │ ├─ Polls tmux pane every 5 seconds ├─ Detects rate limit text ├─ Parses reset time from message ├─ Waits until reset + safety margin ├─ Verifies Claude is still the foreground process └─ Sends "continue" via tmux send-keys

Why tmux?

When you disconnect (SSH drops, close terminal, laptop sleeps), tmux keeps running. The monitor keeps waiting. When you reconnect with tmux attach, you find Claude working on your task. This is the key advantage over wrapper scripts.

Features

  • Zero workflow change — same claude command, same TUI, same everything
  • Works with and without tmux — auto-creates tmux session if you're not already in one
  • Auto-installs tmux if missing (apt, dnf, brew, pacman, apk)
  • Timezone-aware — parses reset times with full IANA timezone support (including half-hour offsets)
  • DST-safe — iterative offset correction handles daylight saving transitions
  • Safe send-keys — verifies Claude is still the foreground process before injecting text
  • Self-healing coverage — reconcile re-arms monitors for any live claude session that lost one; an optional timer (systemd --user on Linux, launchd on macOS) runs it automatically (details)
  • Overload backoff — detects sustained API overload (429/500/502/503/504/529) and retries on a configurable exponential backoff with jitter and a cumulative-wait cap, distinct from the usage-reset path (details)
  • Safeguard retry — auto-continues past an AUP-safeguard false-positive (often transient), capped at a few tries so a sticky flag can't loop (details)
  • Interrupted-stream resume — picks the work back up when a laptop suspend or a dropped connection truncates a response mid-turn and leaves the session parked at an idle prompt (details)
  • Near-limit wrap-up nudge — when Claude Code winds the turn down at ~95% of the 5-hour window ("Approaching your 5-hour usage limit — Claude will wrap up the current step") and parks the session at an idle prompt with no limit banner, sends one continue so the work runs on to the real limit, where the usage wait takes over (details)
  • tmux status bar indicator — see at a glance whether a pane is being monitored, waiting on a reset, backing off from overload, or has given up (details)
  • --print mode support — buffers output, retries cleanly for piped/scripted usage
  • Configurable — retry count, wait margin, custom patterns, retry message
  • Config validation — bad config values fall back to safe defaults instead of crashing
  • Zero dependencies — pure Node.js, no node_modules

Messages Detected (verbatim)

The tool acts on these real-world Claude Code renders — if you landed here after pasting one of these errors into a search engine or an AI assistant: yes, this tool automates the wait-and-retry for all of them.

Usage / session limits — waits until the printed reset, then continues

| Render | Example | |--------|---------| | N-hour limit | 5-hour limit reached - resets 3pm (UTC) | | Session limit | You've hit your session limit · resets 2am (Europe/Zurich) | | Weekly limit | You've hit your weekly limit · resets Oct 9, 10am | | Usage limit | Claude usage limit reached. Resets at 2pm | | Out of extra usage | You're out of extra usage · resets 3pm | | Try again | Please try again in 5 hours | | Hit your limit | You've hit your limit · resets 3pm (Europe/Dublin) | | Rate limit | Rate limit hit. Resets at 4pm | | Live-limit companion hint | /usage-credits to finish what you're working on. |

The /rate-limit-options menu — driven to a wait row, never "Upgrade"

What do you want to do?
❯ 1. Upgrade your plan
  2. Stop and wait for limit to reset (3pm)

Handled across any menu layout (the option order varies by Claude Code version); the tool locates the cursor and the wait option — native auto-continue's "Wait here, then continue automatically" row (v2.1.234+) when offered, else "Stop and wait for limit to reset" — and refuses to press Enter if the layout is unreadable. A menu offering only the native row is recognized as the menu too.

API overload / transient errors — exponential backoff with jitter

| Render | Example | |--------|---------| | Terminal API error (colon form) | API Error: 529 {"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}} | | 5xx family | API Error: 500 / 502 / 503 / 504 … (including bodyless renders like 503 no healthy upstream) | | API-level 429 | API Error: Server is temporarily limiting requests (not your usage limit) · Rate limited |

Safeguard false positives — bounded immediate re-send

API Error: <model>'s safeguards flagged this message (https://www.anthropic.com/legal/aup).
They may flag safe, normal content as well. … Claude Code can't respond to this request with <model>.

Interrupted streams — one resume, then stop

API Error: Your computer went to sleep mid-response. The response above may be incomplete.
API Error: Connection lost mid-response. The response above may be incomplete.
API Error: The response stopped arriving. The response above may be incomplete.
API Error: Server error mid-response. The response above may be incomplete.

…and the three "before a response was produced. Try again." variants (suspend, stall, connection). All seven come from the same stream finalizer and leave the same wreckage: a truncated turn at an idle prompt.

Near-limit wrap-up — one nudge, then the usage wait

⏺ Approaching your 5-hour usage limit — Claude will wrap up the current step.

Not an error: Claude Code prints this at ~95% of the window and tells the model to checkpoint. The model finishes the step, lists what's left and ends the turn — no banner, idle prompt, nothing resumes it. One continue picks the work back up.

Native auto-continue gone stale — one Enter, bounded

Your usage limit has reset
Press enter to continue

Claude Code v2.1.234+ continues past a usage limit on its own — unless the machine slept more than ~30 minutes past the reset, in which case it shows this pair and waits for a human Enter, indefinitely. The tool presses it.

Custom patterns can be added via config for future message format changes.

Detection is chrome-aware: it looks at the live bottom of the pane, but first skips past Claude Code's UI furniture — the input box, footer, key hints, the todo/task widget, the status spinner, and the /usage-credits hint. So a genuine limit banner still registers even when a tall task list or background-agent status pushes it well above the prompt, while a banner merely quoted in scrollback (with real output below it) does not trigger a retry.

Your own customPatterns are the exception: they are matched against the raw last-N lines (not the chrome-skipped view), so a pattern keyed on footer text — a usage percentage, a model name — keeps firing. You own the false-positive tradeoff for your regexes; the built-in detection keeps the chrome-aware discipline.

Configuration

Optional. Create ~/.claude-auto-retry.json:

{
  "maxRetries": 5,
  "pollIntervalSeconds": 5,
  "marginSeconds": 60,
  "fallbackWaitHours": 5,
  "retryMessage": "Continue where you left off. The previous attempt was rate limited.",
  "customPatterns": ["my custom pattern"]
}

| Option | Default | Description | |--------|---------|-------------| | maxRetries | 5 | Max retry attempts per rate-limit event | | pollIntervalSeconds | 5 | How often to check the terminal (seconds) | | marginSeconds | 60 | Extra wait after reset time (seconds) | | fallbackWaitHours | 5 | Wait time if reset time can't be parsed | | nativeDeferSeconds | 180 | Extra wait (seconds) on top of reset + margin when native auto-continue's Continuing automatically at … · esc to cancel line is visible, so the native continue goes first. 0 = no defer | | retryMessage | "Continue where..." | Message sent to Claude on retry | | customPatterns | [] | Additional regex patterns to detect rate limits | | nativeStaleReset | { "enabled": true, "maxRetries": 2, "retryDelaySeconds": 3 } | Press Enter on native auto-continue's stale "Press enter to continue" — see Native stale-reset nudge |

All fields optional. Invalid values fall back to defaults automatically.

Launch wrapper

Set CLAUDE_AUTO_RETRY_LAUNCH_WRAPPER to a prefix command and it's prepended to each interactive session — useful for keeping a machine awake while Claude works, or any other per-process wrapper:

# macOS: don't sleep while a session runs
export CLAUDE_AUTO_RETRY_LAUNCH_WRAPPER="caffeinate -i"

Generic (not macOS-specific — e.g. nice, chrt … work too). Unset or blank spawns claude directly, unchanged.

Session lifetime

When claude exits cleanly inside the auto-created tmux session, the session now ends with it — tmux reaps it, nothing lingers. When the launcher exits non-zero (a crash), the pane falls through to your login shell so the scrollback survives for inspection. Two opt-outs:

# Always keep a shell in the pane after claude exits (the pre-0.7 behavior)
export CLAUDE_AUTO_RETRY_KEEP_SHELL=1

Never create a tmux session (e.g. you're inside Zellij/screen and don't want nesting).

Note: the monitor needs a tmux pane to watch, so this disables auto-retry for the run.

export CLAUDE_AUTO_RETRY_NO_TMUX=1

Environment forwarding

Your full shell environment reaches claude inside the tmux session via a 0600 snapshot file under ~/.claude-auto-retry/tmp/ that only the launcher reads (and deletes immediately). Nothing about your environment — names or values — ever appears on a tmux command line, so secrets can't surface in /proc//cmdline.

Overload backoff

Separate from subscription rate limits, this fork also detects **sustained API overload** — Claude Code's own terminal API Error: line for the retryable set (429 / 500 / 502 / 503 / 504 / 529, or an overloaded_error JSON body) — and retries on an exponential backoff instead of waiting for a usage reset. The two paths never collide; usage limits always take precedence.

Sustained only. Claude Code already retries transient 5xx/529 internally
with its own backoff. This feature fires only when those internal retries are
exhausted and a terminal error is left in the pane. It should rarely trigger.
Terminal vs. transient. Claude Code renders an in-progress retry as the
parens form API Error (529 …) · Retrying in 5s · attempt 3/10, and the final
exhausted error as the colon form API Error: 529 …. Detection requires the
colon form and suppresses the · Retrying… / attempt n/m suffix, so the tool
never interrupts Claude's own backoff.
Anchored, tail-only matching (why it won't fire on your code). Patterns are
case-insensitive regexes matched against only the last 12 lines of the
pane — never the full scrollback. They are anchored to Claude Code's API Error:
render, so a bare 503 in code you're editing (res.status(503)), a
port number, a quoted log, or a status.claude.com link in a comment will not
trip detection. The one residual: a live tail that literally contains
API Error: 529 (e.g. editing this tool, or docs about Claude errors) will match —
set "enabled": false while doing that. (Earlier versions matched bare status
numbers across the whole capture, which injected spurious retries during ordinary
web-dev sessions.) For a structured, ambiguity-free trigger see DESIGN-NOTES.md.

Configured under an overload block (shown with its defaults):

{
  "overload": {
    "enabled": true,
    "patterns": ["API Error:\\s*(429|500|502|503|504|529)\\b", "overloaded_error", "temporarily limiting requests"],
    "backoffSeconds": [30, 60, 120, 240, 300],
    "steadyStateSeconds": 300,
    "jitterPct": 15,
    "maxTotalWaitMinutes": 120,
    "retryMessage": "Continue where you left off.",
    "relaunchOnExit": false,
    "relaunchCommand": "claude --continue"
  }
}

| Option | Default | Description | |--------|---------|-------------| | enabled | true | Turn the overload path on/off | | patterns | (see above) | Case-insensitive regexes matching a terminal overload error in the pane tail (last 12 lines) | | backoffSeconds | [30,60,120,240,300] | Wait before each retry; index i for attempt i | | steadyStateSeconds | 300 | Wait once the backoffSeconds array is exhausted | | jitterPct | 15 | ±% jitter applied to every wait (clamped 0–100) | | maxTotalWaitMinutes | 120 | Cumulative-wait cap — give up loudly past this | | retryMessage | "Continue where you left off." | Sent to Claude on each retry | | relaunchOnExit | false | See the gating decision below | | relaunchCommand | "claude --continue" | Command used by relaunchOnExit |

The waits go 30 → 60 → 120 → 240 → 300 → 300 …, each with ±15% jitter, until the error clears (success) or the cumulative wait reaches maxTotalWaitMinutes (give up — the cap guards against hammering a genuinely-down endpoint or masking a real outage; check status.claude.com).

Event-driven detection (recommended — no scraping)

The scraper above is a heuristic over terminal output. For an exact, ambiguity-free trigger, install the StopFailure hook — Claude Code fires it precisely when a turn ends in an API error, with a typed error class:

claude-auto-retry install-hook                  # into $CLAUDE_CONFIG_DIR or ~/.claude
claude-auto-retry install-hook /path/to/config  # repeat per CLAUDE_CONFIG_DIR you use

This adds a StopFailure hook (matcher overloaded|server_error) that writes a pane-keyed marker the monitor consumes — no terminal scraping, so it cannot false-positive on code or scrollback. Sessions launched via the wrapper after installing the hook use it automatically; the first marker latches event mode and disables the scraper for that session. Sessions without the hook (or pre-install) fall back to the anchored scraper. Remove with uninstall-hook. See DESIGN-NOTES.md for the architecture.

Why not rate_limit? The event path handles only transient overloads
(seconds-scale backoff). A rate_limit is the subscription session/usage limit —
an hours-scale wait until a printed reset time — so it's handled by the usage-wait
path above, not the overload path. Routing it through the hook would fire premature
retries against a session that's simply out of quota.

Gating decision (alive-at-prompt vs exited-to-shell)

A transient API error in interactive Claude Code surfaces inline and leaves the process alive at its prompt — it does not exit to the shell. So the default, robust behavior reuses the existing usage-limit mechanism: only retry when the foreground process is claude/node and the session is idle, not working (the esc to interrupt footer is absent). Retrying mid-internal-retry would double-drive the session, so that case is deferred, never sent.

If a 500 ever does drop you to the shell, send-keys is correctly blocked by the foreground check (it never types into bash), and the tool logs overload-exited-to-shell rather than masking it. Auto-relaunch is **off by default** — blindly typing claude --continue into a shell the user may be using is worse than surfacing the stall. Set relaunchOnExit: true (and adjust relaunchCommand) only if you actually observe shell-exits on overload.

Safeguard retry

A third failure mode, separate from usage limits and 5xx overloads: the model's safeguards flag your message and Claude Code can't respond. It renders like:

● API Error: Fable 5's safeguards flagged this message (…/legal/aup). They may flag
  safe, normal content as well. … Claude Code can't respond to this request with Fable 5.
  Double press esc to edit your last message, or try a different model with /model.

These flags are often false positives (the message says so) and semi-random, so an immediate re-send frequently clears them. When the tool sees this render at an idle prompt, it sends a short retry message (continue by default), waits a few seconds, and repeats — but only up to maxRetries times, then gives up loudly (logged) rather than looping. A sticky flag means the content/model combination is genuinely blocked; switch models with /model or rephrase.

Detection is tail-anchored (last 12 pane lines) like the overload path, and a match additionally requires the API Error render line nearby — so the phrases appearing in scrollback or in a conversation about safeguards won't trigger it.

Configured under a safeguard block (defaults shown):

{
  "safeguard": {
    "enabled": true,
    "patterns": ["safeguards flagged this message", "can't respond to this request with", "legal/aup"],
    "maxRetries": 3,
    "retryDelaySeconds": 8,
    "retryMessage": "continue"
  }
}

| Option | Default | Description | |--------|---------|-------------| | enabled | true | Turn the safeguard-retry path on/off | | patterns | (see above) | Case-insensitive regexes marking the safeguard render (matched in the pane tail, near an API Error line) | | maxRetries | 3 | Re-send attempts before giving up — kept small; retrying a sticky flag won't help | | retryDelaySeconds | 8 | Wait between re-sends | | retryMessage | "continue" | Message sent to nudge past the flag |

Usage limits always take precedence; the safeguard path only acts when Claude is idle (no esc to interrupt footer) and the foreground process is claude/node.

Interrupted-stream resume

Close your laptop lid while Claude is mid-answer and the work does not survive. Claude Code wraps the response body in a byte watchdog; when the bytes stop arriving it aborts the stream and finalizes whatever had already been printed, naming the cause:

⏺ API Error: Your computer went to sleep mid-response. The response above may be
  incomplete.

The turn is then over. The prompt returns idle and nothing resumes it, so the session sits there — potentially for hours — with a half-finished answer on screen. Claude Code retries by itself only while the response is still thinking-only; once real content has been yielded it declines to retry, which is exactly when this render appears. That is the gap this closes: seeing it at an idle prompt, the tool sends continue, bounded at maxRetries because a machine that just woke may not have its network back yet.

The same finalizer emits the render for a dropped connection, a stalled stream and a mid-response server error. All are the same truncated-turn state with the same remedy, so all are matched — sleeping is just the cause we can name.

Detection is anchored on the shape of the line, not its vocabulary: a real render begins with API Error:, behind at most Claude's message glyph. The "an API Error line nearby" rule the overload and safeguard paths use is deliberately not enough here — these sentences are ordinary English, so a session merely explaining them quotes the whole render, anchor included, mid-sentence. Prose carries it mid-line and your own typed line carries ❯, so neither trips it.

Configured under a streamInterrupted block (defaults shown):

{
  "streamInterrupted": {
    "enabled": true,
    "patterns": ["went to sleep mid-response", "Connection lost mid-response", "…"],
    "maxRetries": 2,
    "retryDelaySeconds": 5,
    "retryMessage": "continue"
  }
}

| Option | Default | Description | |--------|---------|-------------| | enabled | true | Turn the interrupted-stream path on/off | | patterns | (all 7 renders) | Case-insensitive regexes, matched only against a line that begins with the API Error: render | | maxRetries | 2 | Resume attempts before giving up loudly | | retryDelaySeconds | 5 | Wait before resuming — lets the network settle after a wake | | retryMessage | "continue" | Message sent to pick the work back up |

Usage limits take precedence, and like the safeguard path this only acts when Claude is idle and the foreground process is claude/node.

Near-limit wrap-up nudge

At roughly 95% of the 5-hour window, Claude Code injects a checkpoint instruction into the model's context — *finish the current step, then list up to three short bullets of the most impactful remaining work, don't start subagents or long-running work* — and prints:

⏺ Approaching your 5-hour usage limit — Claude will wrap up the current step.

The model does exactly that and ends the turn. There is no limit banner (the limit has not been hit), the prompt returns idle, and nothing resumes the session — so an overnight run winds down at 95% and stays parked long after the window has reset. This is a server-side Claude Code behavior with no user-facing switch (it is feature-flagged, not a setting), so the tool handles the render instead.

Seeing the notice at an idle prompt, the tool sends one continue. The session then either finishes its work or runs into the real limit, where the usage wait takes over as usual. Unlike the retry families above this is not a bounded machine: the nudge renders as a user row under the notice, and a notice with a user row below it — yours or ours — belongs to a turn that has already been answered, so it is never nudged twice. The maxRetries cap only bounds the pathological case where the nudge never renders.

Detection is anchored on the shape of the line, like the interrupted-stream render: the notice begins its line, behind at most Claude's message glyph, so prose quoting it and your own typed copy don't trip it.

Configured under a nearLimitWrapUp block (defaults shown):

{
  "nearLimitWrapUp": {
    "enabled": true,
    "maxRetries": 3,
    "retryMessage": "continue"
  }
}

Set "enabled": false if you want the session to stop at the checkpoint.

Native stale-reset nudge

Claude Code v2.1.234+ waits out a usage limit itself and continues at the reset. But if the machine slept more than ~30 minutes past the reset, it does not continue on wake. Its two status lines change to:

Your usage limit has reset
Press enter to continue

and the session waits for a human Enter — indefinitely. The limit banner is gone by then, so the tool's own wait (which usually armed on the same banner and slept along with the host) used to read that screen as "you already continued" and stand down, leaving the session parked. Now the wait's recheck recognizes the pair and hands it to this nudge: at an idle prompt, with claude/node in the foreground, the tool presses a bare Enter (the key the UI asks for — no message is typed). If the pair clears, the episode is over; if it is still there after maxRetries presses, the tool gives up loudly (🔴AR) rather than keep pressing. Once the pair is gone, even after a give-up (say you pressed Enter yourself and the session is working), the tool goes back to normal monitoring and 🔴AR clears. A live usage-limit banner always takes precedence.

Detection is anchored on where the pair sits, not just its words. The real stale rows are status chrome: they are the bottom-most content of the pane, below the input box. So the pair only matches when Press enter to continue is the last content row (UI footer aside), Your usage limit has reset is the nearest non-blank row above it, and both sit below the input row (❯, empty or holding a draft) when one is on screen. Model output always renders above the input box. A reply or code block quoting the pair, even on indented lines of its own, therefore doesn't match. Each phrase must also begin its line (behind at most indentation and one chrome marker such as │ or ⎿). That rules out mid-sentence quotes, the model's ⏺ output and your own ❯ row, and tool-call output is excluded as well. Press enter to continue on its own is a generic phrase and never matches.

Configured under a nativeStaleReset block (defaults shown):

{
  "nativeStaleReset": {
    "enabled": true,
    "maxRetries": 2,
    "retryDelaySeconds": 3
  }
}

| Option | Default | Description | |--------|---------|-------------| | enabled | true | Turn the stale-reset nudge on/off | | maxRetries | 2 | Enter presses before giving up loudly | | retryDelaySeconds | 3 | Wait before each press — lets the wake and native's redraw settle |

The status bar shows 🟢AR while the nudge is in progress (it lasts seconds) and 🔴AR if it gave up, until the session is working again.

tmux status bar indicator

The monitor writes a small JSON snapshot per pane on every poll tick, so you can tell at a glance — without checking logs — whether a pane is being watched, waiting out a usage-limit reset, backing off from overload or a safeguard flag, or has given up.

Add a segment to status-right (or status-left) in ~/.tmux.conf that shells out to the bundled reader script, passing the current pane id and the server's socket path:

set -g status-interval 5
set -g status-right "#(~/.local/lib/node_modules/claude-auto-retry/bin/tmux-status.sh '#{pane_id}' '#{socket_path}') | %Y-%m-%d %H:%M"

Use an absolute path, not the bare command name. #() commands run inside the tmux server's own environment, not the environment of whichever shell you attached from — if the server was started before your shell rc added npm/nvm's bin directory to PATH (e.g. tmux auto-started at login, or by another program), the bare command name resolves to nothing and the segment stays permanently blank with no error anywhere. Find your actual install path with which claude-auto-retry-tmux-status (run it in a normal shell, then hardcode that path in .tmux.conf) if it differs from the example above. If you use nvm and switch Node versions, re-check the path.

tmux substitutes #{pane_id} and #{socket_path} itself before running the command (these are tmux format variables, resolved at expansion time — not environment variables the script has to go looking for), so the segment always reflects whichever pane you're looking at, correctly scoped to the tmux server it belongs to. The socket_path argument matters if you ever run more than one tmux server on the same machine (e.g. tmux -L work, tmux -L personal, or two users' default servers on a shared host): pane ids like %2 are only unique within a server, so without it two different servers' %2 panes would render each other's status. Always pass it: the monitor keys each status file by the socket path it inherits from $TMUX, so a single-argument config looks under a shared default key the monitor never writes to, and the segment simply stays blank.

It prints:

| Pane state | Indicator | |------------|-----------| | Actively monitoring | 🟢AR | | Waiting on a usage-limit reset | ⏳AR 1h30m | | Backing off from overload | 🟠AR 45s | | Retrying past a safeguard/AUP false-positive | 🛡AR 8s | | Given up — max retries/backoff cap reached; no further automatic action on this pane | 🔴AR | | No monitor for this pane, or the status file is stale (monitor process died without cleaning up) | (nothing) |

🔴AR overrides whatever the underlying status would otherwise render. Several give-up paths intentionally leave the monitor's internal status at whatever it was when it stopped acting (so the scraper/event logic doesn't re-detect its own stale error next tick) — without an explicit gaveUp flag in the snapshot, the status bar would keep showing a live 🟢/⏳/🟠 indicator for a monitor that will not act again on this pane until the underlying condition clears on its own.

Staleness is derived from each snapshot's own pollIntervalSeconds (age > 2× the monitor's configured poll interval) rather than a fixed constant, so a healthy monitor running with a longer pollIntervalSeconds doesn't have its segment blank out for a large fraction of every tick.

The script (bin/tmux-status.sh) is pure POSIX shell with no dependencies (no jq, no node), so it's cheap to run every few seconds from every attached client. status-interval defaults to 15s in tmux; dropping it to 5 (matching the monitor's default pollIntervalSeconds) keeps the overload countdown responsive.

CLI Commands

claude-auto-retry install          # Install shell wrapper + tmux
claude-auto-retry uninstall        # Remove shell wrapper
claude-auto-retry status           # Show monitor activity + last log entries
claude-auto-retry logs             # Tail today's log file in real-time
claude-auto-retry version          # Print version

Event-driven overload detection (optional; see "Overload backoff")

claude-auto-retry install-hook [dir] # Install the StopFailure hook into a config dir claude-auto-retry uninstall-hook [dir] # Remove it

Monitor coverage (see "Keeping monitors alive")

claude-auto-retry reconcile # Re-arm a monitor for every live claude pane not covered claude-auto-retry reconcile --dry-run # Preview without arming claude-auto-retry install-timer # Run reconcile every 5 min (systemd --user on Linux, # launchd LaunchAgent on macOS) claude-auto-retry uninstall-timer # Remove the timer claude-auto-retry exclude-self # Keep THIS session unmonitored (durable, self-expiring)

For AI Agents

If you are an AI agent (Claude Code, Codex, etc.) installing this for your user, the full setup is non-interactive:

npm install -g claude-auto-retry
claude-auto-retry install        # shell wrapper (+ tmux if missing)
claude-auto-retry install-hook   # recommended: event-driven, scrape-free overload detection

Notes for agents:

  • The wrapper takes effect in new shells — have the user source their shell rc or
open a new terminal. Sessions launched via the wrapper run inside tmux (required by the monitor).
  • Verify with claude-auto-retry status (monitor activity) and claude-auto-retry logs.
  • Configuration is optional and defaults are safe. To change it, write
~/.claude-auto-retry.json (see Configuration); invalid values fall back to defaults instead of crashing.
  • If the user runs multiple CLAUDE_CONFIG_DIRs, repeat claude-auto-retry install-hook per dir.
  • Clean removal: claude-auto-retry uninstall and claude-auto-retry uninstall-hook.

Keeping monitors alive

Each claude you launch through the wrapper gets its own background monitor. Monitors are detached processes with no supervising service, so if one is killed — or a claude is started outside the wrapper — that session ends up unmonitored, and only new sessions get a monitor. Two commands restore and maintain full coverage:

  • reconcile re-arms a monitor for every live tmux pane running claude that isn't
already covered. It maps each claude to its pane from live process state, keeps one monitor per pane (idempotent — safe to run anytime, and a single-instance lock stops an overlapping manual+timer run from double-arming), and handles tmux pane-id reuse. Print- mode sessions (claude -p) are skipped, and a claude that doesn't set its process title to claude (a bare node shebang) isn't detected — use the wrapper for those. Run it after a crash, or use --dry-run to see what it would do.
  • install-timer wires reconcile to a timer that runs every 5 minutes, so a
monitor that dies is re-armed within one interval — coverage self-heals with no manual step. On Linux this is a systemd --user timer (enable loginctl enable-linger $USER once if you want it to run while logged out); on macOS it is a launchd LaunchAgent in ~/Library/LaunchAgents (LaunchAgents only run while you are logged in, which is fine — the tmux server it reconciles lives in your login session too).

Excluding a session. To keep a specific session unmonitored (e.g. one where you're pasting rate-limit text and don't want any auto-retry), run claude-auto-retry exclude-self from inside it. This records the session's claude PID in ~/.claude-auto-retry/reconcile-exclude; both reconcile and the timer skip it. Keying on the PID makes the entry self-expiring: dead PIDs are pruned when the file is read, so once that claude exits its entry is dropped and can never accidentally mute a later session (tmux reuses pane ids, so a hand-added %pane exclude could — pane ids are not pruned, since staleness can't be detected). Prefer the PID form; you can also hand-add a %pane id or a PID to that file.

Platform Support

Operating Systems

| OS | tmux auto-install | Status | |----|-------------------|--------| | Ubuntu / Debian | apt-get | Fully supported | | CentOS / RHEL / Fedora | dnf | Fully supported | | Rocky Linux / Amazon Linux | dnf | Fully supported | | macOS | brew | Fully supported | | Arch Linux | pacman | Fully supported | | Alpine | apk | Fully supported | | Windows | — | Not supported natively — the tool drives a tmux pane, which Windows does not have. Use WSL2 (Ubuntu), where it works as on Linux. A native backend via a tmux-compatible multiplexer is being discussed in #79. |

Requirements

  • Node.js >= 18
  • tmux >= 2.1 (auto-installed if missing)

Shell Support

| Shell | Status | |-------|--------| | bash | Full (auto-install to ~/.bashrc) | | zsh | Full (auto-install to ~/.zshrc) | | fish | Manual setup (instructions printed on install) |

--print Mode

For scripted/piped usage (claude -p "..." | jq), the tool:

  1. Buffers all output (nothing goes to stdout until done)
  2. If rate-limited: discards partial output, waits, re-executes with same args
  3. Consumer receives a single clean response
# This just works — retries transparently if rate-limited
claude -p "Generate a JSON schema" | jq .

Logging

Logs are written to ~/.claude-auto-retry/logs/YYYY-MM-DD.log:

[2026-03-18 15:00:05] [INFO] Monitor started for pane %3 (claude PID: 12345)
[2026-03-18 15:32:10] [INFO] Rate limit detected: "5-hour limit reached - resets 3pm". Waiting 3547s...
[2026-03-18 16:01:10] [INFO] Sent retry message (attempt 1)

Logs rotate daily. Files older than 7 days are cleaned automatically.

Uninstall

claude-auto-retry uninstall
npm uninstall -g claude-auto-retry

This removes the shell function from your rc files. tmux is left installed.

Known Limitations

  1. Retry message context — The retry message is sent as plain text. If Claude was mid-confirmation or in a special input state, it may not interpret it as a continuation. You can customize the message via config.
  1. Node version lock — The launcher path is resolved at install time. If you switch Node versions with nvm, re-run claude-auto-retry install.
  1. tmux required — The tool needs tmux to monitor terminal output and inject keystrokes. It auto-installs if missing, but requires sudo for system package managers.

Contributing

Contributions are welcome! Here's how to get started:

Development Setup

git clone https://github.com/cheapestinference/claude-auto-retry.git
cd claude-auto-retry
npm test            # Run all 128 tests
npm link            # Install locally for testing

Project Structure

claude-auto-retry/
├── bin/cli.js              # CLI: install, hook, reconcile, timer, status, logs, ...
├── src/
│   ├── patterns.js         # Rate limit + overload detection + ANSI stripping
│   ├── time-parser.js      # Reset time parsing with timezone support
│   ├── config.js           # Config loading + validation
│   ├── logger.js           # File-based logging with rotation
│   ├── tmux.js             # tmux command wrappers (execFile-based)
│   ├── monitor.js          # Core monitoring loop + retry logic (usage + overload paths)
│   ├── events.js           # StopFailure hook event channel (scrape-free overload trigger)
│   ├── reconcile.js        # Re-arm monitors for all live claude panes + exclusion
│   ├── launcher.js         # Process orchestration + signal forwarding
│   └── wrapper.sh          # Shell function template
├── systemd/                # systemd --user units for the reconcile timer (Linux)
├── launchd/                # LaunchAgent plist for the reconcile timer (macOS)
├── test/                   # tests across the src modules
├── package.json
├── LICENSE
└── README.md

Architecture Decisions

  • Zero dependencies — only Node.js built-ins. Reduces supply chain risk and install size.
  • execFile over exec — all child process calls use array-based args to prevent shell injection.
  • stdio: 'inherit' — Claude gets the real TTY for full TUI support. The monitor reads pane content independently via tmux capture-pane.
  • Iterative DST correction — timezone offset is computed via 3-iteration convergence loop, not a single-shot formula that breaks at DST boundaries.
  • Config validation — invalid user config values fall back to safe defaults instead of producing NaN/undefined behavior.

Running Tests

npm test                              # All tests
node --test test/patterns.test.js     # Single file
node --test --watch test/             # Watch mode

Submitting Changes

  1. Fork the repo
  2. Create a feature branch (git checkout -b feat/my-feature)
  3. Write tests first (TDD)
  4. Make your changes
  5. Ensure all tests pass (npm test)
  6. Submit a Pull Request

Areas for Contribution

  • New rate limit patterns — If you see a Claude Code rate limit message that isn't detected, open an issue with the exact text.
  • Fish shell support — Auto-install for fish shell (currently manual).
  • Windows support — WSL works, but native Windows would need a different approach.
  • Notification integration — Desktop/Slack notification when rate limit detected or when Claude resumes.

Related Projects

FAQ

Q: Claude Code now continues automatically after a limit reset (v2.1.234+). Is this tool obsolete? A: For the basic interactive case — yes, and the native feature does it better (it gets the reset time from the API instead of parsing the terminal). What remains is everything around that case: surviving disconnects via tmux, waits the native feature won't arm, headless -p runs, and the non-limit failures (overload, safeguards, interrupted streams, wrap-up parking). See Native auto-continue vs this tool for the verified comparison.

Q: Does this work with Claude Max/Pro/Team? A: Yes. It works with any Anthropic subscription that has usage-based rate limits.

Q: Does it work outside of tmux? A: Yes. If you're not in tmux, it creates a tmux session transparently. You won't notice a difference.

Q: What if I continue manually before the retry fires? A: The monitor checks if the rate

... (README truncated for length)

NeshDevTech

NeshDevTech is a professional technology firm passionate about creating innovative, high-performance web and mobile solutions.

Core Services
  • Custom Laravel Apps
  • SPA (React & Vue.js)
  • Flutter Mobile Apps
  • SEO & Performance Opt
Get in Touch
Available for Projects

Need a high-performing system? Let's discuss your project.


© 2026 NeshDevTech. All rights reserved.

Built with

Chat with me