When Your Coding Agent Hits a Rate Limit: A Fallback Ladder That Keeps Working
A rate limit, a stalled run that already finished the work, and a pull request that still fails review need three different responses. Here is the ladder I use to tell them apart.

Updated: October 2026. Free model availability changes weekly; the method below does not.
A small Android fix: a dialog that would not scroll, plus a test that proves the fix. I sent it to a free model. Fifteen seconds later it came back with "This model is not available in your country". The second free model lasted fifteen seconds and said "Model unavailable". The third was rate limited after sixteen.
Three models, 46 seconds, and not a line of code written. If you searched "claude code rate limit reached", it is the same wall, hit three times in a row. The fourth free model took 52 minutes and delivered a pull request with the test proven red before the fix and green after it.
Nothing about that task changed between the third model and the fourth. What changed was the row.
TL;DR: When an AI coding agent runs out of quota, don't wait for the reset window and don't paste the prompt into whatever tool is open. Keep an ordered ladder of models, free first and paid one step at a time. Move to the next row automatically only when the run failed before writing anything. If it wrote something, look at the disk first. If you disliked the result, correct it on the same model. Log every run so the order comes from your own results.
What a rate limit looks like (and what looks like one but is not)
The usual advice for a Claude Code rate limit is to check whether it is an outage, check your usage and wait. Some guides add "switch to another CLI". Neither says which error means switch, or what to do when the run had already written code.
To switch without a human, a script has to recognise "this model cannot take work right now". Each tool says it differently. These are the strings I have seen.
Free model endpoints, which usually fail at the first request:
Rate limit exceeded
Model unavailable
Upstream request failed: Endpoint is unavailable
This model is not available in your country
Both of my build machines have hit the country block, on different days.
Google Antigravity, when a quota runs out:
RESOURCE_EXHAUSTED (code 429): Individual quota reached. ... Resets in NNh
Antigravity retries internally before it gives up, so an exhausted run takes about 100 seconds to come back, empty.
Cursor Composer:
ActionRequiredError: You're out of usage
None of these say anything about the task. They say this endpoint cannot accept work now, so the next row should get it.
One of them misleads, though. Antigravity once returned 429 for every Gemini model I tried. Before writing the tool off, I sent the same check to claude-sonnet-4-6 on the same tool. It succeeded in 2.7 seconds, and so did the other non-Gemini models. Antigravity bills Gemini and non-Gemini models against separate quotas, so one empty pool does not mean the tool is out. My ladder now gives Antigravity two rows, one per pool.
The ladder: free models first, paid rows one step at a time
The dialog fix reached the fourth row in under a minute because the first three rows were free and failed fast. That is the shape I want: cheap rows first, and a failure that costs seconds. For ordinary work (fixes, docs, tests, small features), the order is:
- Free models on opencode, such as
nemotron-3-ultra-free,longcat-2.5-preview-freeandspace-bunny-free. They cost nothing. Their availability changes by the day. - Antigravity, starting with
gemini-3.8-flash-medium, then its non-Gemini models. It runs on a plan that is already paid for. - Paid opencode rows, cheapest first:
deepseek-v4.1-flash, thenmimo-v2.6-flash, then the larger models one at a time. - Cursor Composer, then Devin, which is billed per use, so it comes last of the paid tools.
- Claude Sonnet, last.
One kind of work skips this order. A task that needs more reasoning (an architecture change, a migration, concurrency, a release-blocking root cause) goes straight to Sonnet, but only with an explicit approval for that task and a written reason.
Every new task starts again at row one, even if the previous task ended on a paid row. In the period I tracked, the single biggest paid spend was one task on a large paid model. Resetting per task stops that from becoming the default for whatever comes next.
When a failed run already did the work
Every long run sits inside a watchdog, the same one I use for running several Claude Code agents on one machine. Its exit codes are in the previous article: 125 for a stall (no output for 600 seconds by default), 124 for the hard cap (7,200 seconds by default). Neither, on its own, says the code is wrong. A stall is classified from its diagnostics before anyone states a cause. For an agent runner I add one more: exit 3, "exited zero but empty", meaning the run returned no usable report. It does not mean nothing was written.
My own rule card says those exit codes mean "no result was produced". The release-notes upload module shows why that is not enough. The first free row was rate limited before doing anything. The second ran for about nine minutes and exited 125: the connection dropped as it was finishing. Read that way, the run was waste. I looked at the worktree instead. The module and its 24 tests were there, and all 24 passed.
That still was not a pass. The independent review failed it on four real design gaps. I sent the findings back to the same model. The correction round took about two minutes and exited 1 with the work complete. 32 tests passed, and the second review left two minor residuals that were accepted.
Two exits that a simple runner would have counted as failures produced the finished module, and the review that failed it was answered by the same model. Those two facts are the rest of the rule.
Falling through on inability, not on a bad result
So the rule has three branches:
- Cannot run: rate limit, model unavailable, country block, a dead endpoint. If it happened at the start and the worktree is unchanged, move to the next row.
- Ran, but the run broke: a crash, a dropped connection, a stall, an exit with no report. Don't assume nothing happened. Look at the worktree. If the work is there, review it. If it is half there, keep it and send a continuation brief to the same row before you climb.
- Ran, and the result is wrong: failing tests, or an independent review fails it. The model works. Send the findings back to the same model for one correction round. The model that wrote the code already has the context. If one correction round does not fix it, step up one row, not straight to the top.
This is the decision flow, simplified. Only the first branch runs unattended, and only inside the free-model stage: the runner retries the next free model by itself. Every other branch stops, and I (or the agent orchestrating the work) pick the next step by the same rule.
for row in ladder: # reset to the first row for every new task
result = run(row, brief, clean_worktree)
if result.early_unavailable and worktree_unchanged():
log(row, result); continue # automatic for free rows, max 8 tries
if result.exit in (stalled, timed_out):
log(row, result); stop_and_inspect() # never retried automatically
if result.exit != 0 or result.no_report:
stop_and_inspect() # work may be on disk
review(result) # tests plus an independent reviewer
if rejected: correct_once(row) # same row, then one step up
break
if ladder exhausted: report blocked # don't take the task over by hand
The automatic branch is deliberately narrow: a rate limit, unavailable model, country block or dead endpoint, before any tool call, with the worktree unchanged, at most eight tries, and never after a stall or timeout. My unchanged-worktree check compares the git HEAD and status. That misses one case: an extra edit to a file that was already dirty. So I dispatch on a clean worktree, and rate limits arrive at the first API call anyway.
Which rows worked, from my own journal
Benchmarks change weekly, so I order the ladder from a journal. Every dispatch gets one row: the task, the exact model id, estimated and actual minutes, the exit code and the outcome.
| Task | Model | Time | Exit | Outcome |
|---|---|---|---|---|
| UI component, continuation run | space-bunny-free | 1 min | 0 | No output and no changes after 60s; fell through |
| UI component, continuation | muse-spark-1.3-contributor-free | 15s | 1 | "Rate limit exceeded"; fell through |
| UI component, continuation | nemotron-3-ultra-free | 15s | 1 | "Rate limit exceeded"; fell through |
| UI component, continuation | gemini-3.8-flash-medium (Antigravity) | 44 min | 0 | PR opened, 5 commits; Android snapshot verify 0 changed, iOS snapshots OK |
| Dialog scroll fix with test | muse-spark-1.3-contributor-free | 15s | 1 | "This model is not available in your country"; fell through |
| Dialog scroll fix | space-bunny-free | 15s | 1 | "Model unavailable"; fell through |
| Dialog scroll fix | nemotron-3-ultra-free | 16s | 1 | Rate limited; fell through |
| Dialog scroll fix | longcat-2.5-preview-free | 52 min | 0 | PR delivered, test proven red then green |
| Release notes module | muse-spark-1.3-contributor-free | under 1 min | not recorded | Rate limited at start, no result; fell through |
| Release notes module | space-bunny-free | about 9 min | 125 | Connection dropped after the work was done; 24/24 tests pass on review; independent review FAIL on 4 design gaps |
| Release notes, correction round | space-bunny-free | about 2 min | 1 | Work complete; 32 tests pass; second review left 2 minor residuals, accepted |
Table 1: The three early dialog failures totalled 46 seconds. The non-zero exits in the last two rows still left completed work.
The UI component rows were a continuation: an earlier run on the same component had already committed the Android side before its connection dropped. The table shows where the rest of the work went.
The journal also tracks speed. A run that takes more than 1.5 times its estimate is marked unreliable for that kind of task, and I reorder the ladder by hand from those marks.
What I would not automate
The automatic part covers one decision only: this row could not start, so try the next one. Everything else stops for a person.
History rewrites are banned in every worker prompt: no force-push, no reset --hard on a pushed branch, no rebase or amend of pushed commits. Workers only add commits. I added that rule after an agent squashed and force-pushed a pull request without being asked. The content survived, but a force-push is a decision I make myself.
Climbing to the expensive tier needs an approval for that task, so a runner stuck in a loop cannot spend its way up.
Review stays independent. The model that wrote the change never signs it off. On the release-notes module, the author's own 24 passing tests did not catch the four gaps the reviewer found.
Next time a model runs out of quota: if it failed at the start and wrote nothing, give the task to the next row. If it wrote something, review it or continue it on the same model. If the review fails it, correct it on the same model once before you climb. Then write down which row finished it.
Keep reading
- Twelve Cores, Seven Devices, One DiskGit worktrees keep parallel agents out of each other's files. They do nothing about the CPU, the test phones, the disk or the run that hung seven hours ago. Those are the parts that actually broke.
- Don't Trust an Agent That Says the Tests PassedAn agent's report is a claim. The setup I run only accepts evidence a machine or a second agent can check, and the cases where that caught something are on the page.
- How I Review AI Changes in a Production Mobile AppThe coder doesn't get to grade its own homework. A working method for putting AI on a production codebase — one agent that plans and reviews, one that writes, and durable state that lives in files instead of the chat.