Ramiz RajaBook a call
← Writing
Working With AI Agents

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.

Ramiz RajaOctober 21, 20268 min read
When Your Coding Agent Hits a Rate Limit: A Fallback Ladder That Keeps Working

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:

  1. Free models on opencode, such as nemotron-3-ultra-free, longcat-2.5-preview-free and space-bunny-free. They cost nothing. Their availability changes by the day.
  2. Antigravity, starting with gemini-3.8-flash-medium, then its non-Gemini models. It runs on a plan that is already paid for.
  3. Paid opencode rows, cheapest first: deepseek-v4.1-flash, then mimo-v2.6-flash, then the larger models one at a time.
  4. Cursor Composer, then Devin, which is billed per use, so it comes last of the paid tools.
  5. 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.

TaskModelTimeExitOutcome
UI component, continuation runspace-bunny-free1 min0No output and no changes after 60s; fell through
UI component, continuationmuse-spark-1.3-contributor-free15s1"Rate limit exceeded"; fell through
UI component, continuationnemotron-3-ultra-free15s1"Rate limit exceeded"; fell through
UI component, continuationgemini-3.8-flash-medium (Antigravity)44 min0PR opened, 5 commits; Android snapshot verify 0 changed, iOS snapshots OK
Dialog scroll fix with testmuse-spark-1.3-contributor-free15s1"This model is not available in your country"; fell through
Dialog scroll fixspace-bunny-free15s1"Model unavailable"; fell through
Dialog scroll fixnemotron-3-ultra-free16s1Rate limited; fell through
Dialog scroll fixlongcat-2.5-preview-free52 min0PR delivered, test proven red then green
Release notes modulemuse-spark-1.3-contributor-freeunder 1 minnot recordedRate limited at start, no result; fell through
Release notes modulespace-bunny-freeabout 9 min125Connection dropped after the work was done; 24/24 tests pass on review; independent review FAIL on 4 design gaps
Release notes, correction roundspace-bunny-freeabout 2 min1Work 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.