Your turn died on an API error while you were away. This types continue for you.
No tmux. No screen scraping. No stolen focus. No extra process running in the background.
Claude Code retries transient API failures on its own. When those retries run out, the turn is aborted and the session goes quiet at the prompt, waiting for a human to nudge it:
API Error: 529 overloaded_error - Retrying in 8s... (attempt 10/10)
x API Error
>
If you stepped away, or that session is one of a dozen terminal windows you are not looking at,
a five-second network blip costs you an hour of wall-clock time. Every fix is the same fix:
type continue.
turn dies (overloaded / server error / connection dropped)
|
+- StopFailure hook fires
|
+- wait 10s -> type "continue" + Enter into that session -> ding
|
+- died again? wait 30s -> one more try
|
+- still failing -> alert sound, stop, wait for a human
A turn that completes normally clears the streak, so "two in a row" really means in a row.
Two pieces, and the second one is the interesting one.
1. StopFailure - a hook that fires on exactly this event.
Since Claude Code 2.1.241 there is a StopFailure hook event, fired when a turn ends because
of an API error. Its payload tells you what went wrong:
{
"hook_event_name": "StopFailure",
"session_id": "...",
"error": "overloaded",
"error_details": "...",
"last_assistant_message": "..."
}The matcher field filters on error, so you can react to a 529 without reacting to an
expired login. What the hook cannot do is resume the turn - unlike Stop, its output and
exit code are ignored (OnApiRetry, which would have allowed that, was closed as not
planned).
2. WriteConsoleInput - putting the keystrokes back where they belong.
So the continue has to come from outside, as if you had typed it. The usual tricks are all
bad: SendKeys needs the window focused, screen scraping needs tmux, and both need to figure
out which of your terminal windows owns the session that failed.
None of that is necessary, because every child process a Claude Code hook spawns is attached
to the same Windows console as the claude process that spawned it. Check for yourself -
call GetConsoleProcessList from inside a hook and claude is right there in the list:
attached=3
7020 pwsh
2340 claude
6568 powershell
Which means a hook can open CONIN$ and call WriteConsoleInput to push synthetic key events
straight into the input buffer its own session is reading from. The text appears in the prompt
exactly as if it were typed:
- no window hunting - the console is the session that failed;
- no focus stealing - you can keep working in another window while it happens;
- correct by construction with any number of parallel sessions;
- works in Windows Terminal, conhost, the VS Code terminal, anything ConPTY-backed.
| OS | Windows (uses the Win32 console API) |
| Claude Code | 2.1.241 or newer - earlier versions have no StopFailure event |
| PowerShell | 5.1 (built in) or PowerShell 7+ |
git clone https://github.com/danvak/claude-code-auto-continue.git
cd claude-code-auto-continue
.\install.ps1The installer backs up settings.json, keeps every other setting and hook you already have,
and adds two entries:
Restart your session afterwards - settings.json is read at startup, so a session that was
already open will not have the hooks.
Scope it to a single repository instead of your whole account with
.\install.ps1 -SettingsPath .\.claude\settings.json.
.\install.ps1 -TestFeeds the hook a synthetic StopFailure payload in dry-run mode. Nothing is typed into your
session; it just proves the wiring:
OK hook parsed the payload correctly:
2026-08-24 16:52:20 dry run error=overloaded session=INSTALL-TEST attempt=1/2
You can also confirm the hooks are loaded with /hooks inside Claude Code.
To watch the real thing, tail the log: Get-Content .\auto-continue.log -Wait
Copy config.example.json to config.json (git-ignored, so updates will not clobber it):
| Key | Default | What it does |
|---|---|---|
maxRetries |
2 |
Auto-continues allowed per failure streak |
delaysSec |
[10, 30] |
Seconds to wait before each attempt; the last value repeats |
message |
"continue" |
What gets typed into the session |
forgetAfterMinutes |
30 |
Quiet period after which an old streak is forgotten |
soundOnRetry |
Windows Balloon | Played when a continue is sent; "" to disable |
soundOnGiveUp |
Critical Stop | Played when the budget is spent; "" to disable |
Claude Code classifies API failures into these error values. The default matcher covers the
transient ones - the failures where retrying is the fix:
error |
Retried | Why |
|---|---|---|
overloaded |
yes | 529, the model is busy - the canonical blip |
server_error |
yes | 5xx upstream |
unknown |
yes | where dropped connections and timeouts land |
max_output_tokens |
yes | the turn was cut off mid-thought; continue is literally the fix |
rate_limit |
no | retrying in 10s cannot conjure quota - wait for the reset |
authentication_failed |
no | needs a human to log in |
account_on_hold, billing_error |
no | needs a human with a credit card |
invalid_request, model_not_found |
no | deterministic; retrying reproduces it |
Override with .\install.ps1 -Matcher 'overloaded|server_error'.
| Path | |
|---|---|
auto-continue.ps1 |
The hook: counts failures, waits, sends the keystrokes |
inject-keys.ps1 |
Standalone WriteConsoleInput helper - usable on its own |
install.ps1 |
Wires/unwires the hooks, plus -Test and -Uninstall |
auto-continue.log |
What was sent and when |
.state/<session-id>.txt |
Consecutive-failure counter, per session |
inject-keys.ps1 is useful by itself if you want to script a session:
.\inject-keys.ps1 -Text 'run the tests' -Enter
.\inject-keys.ps1 -Text 'hello' -TargetPid 1234 # from outside that console.\install.ps1 -UninstallRemoves only the hooks this installer added, backs up settings.json first, and leaves the
rest of your configuration alone.
- If you are typing in that window when the injection fires, your text and
continuemerge and get submitted together. Unavoidable - that is what writing into the same input buffer means. ShortendelaysSecif it bothers you. - Windows only. The
StopFailurehalf is cross-platform; theCONIN$half is not. A Linux or macOS port would write to the session TTY withTIOCSTI, or fall back to tmuxsend-keys. - Fires for the main agent only - subagent contexts are skipped by Claude Code itself.
- Tested against a synthetic
StopFailurepayload and against live keystroke injection into a running session. Real outages are, thankfully, hard to schedule.
- cheapestinference/claude-auto-retry - tmux-based, parses the terminal output, waits out subscription rate limits
- mo-arvan/herdr-claude-auto-retry - herdr-native rewrite of the above, no tmux
Both watch Claude Code from the outside, on Unix. This one is driven by Claude Code's own hook system on Windows, so there is no wrapper process and nothing to keep running.
MIT - see LICENSE.