Claude Code 429 怎么办:Request rejected (429) 原文、会重试多久、retry-after 超过 60 秒直接放弃(实测)
先回答你搜的问题
API Error: Request rejected (429)是什么:后端(Anthropic 官方,或者你用的中转站 / 网关)说你请求太多了。·后面那段是后端返回的原话,判断是谁限的流就看这一段。- 要不要手动重试:一般不用。Claude Code 已经自己重试了最多 10 次、大约 3 分钟,你看到报错时说明这 3 分钟里一直在被限流。
- 一瞬间就报错、根本没等:后端给的
retry-after超过了 60 秒,Claude Code 直接放弃了(实测阈值正好在 60 / 61 秒之间)。这种情况等一段时间再发就行,或者找中转站调大限额。- 交互模式下界面一直显示
Retrying in Ns · attempt N/10:这是正常的退避等待,不是卡死,可以按Esc中断。- 月度消费上限触发的 429:官方文档说这类 429 不带
retry-after,额度恢复前会一直失败。重试救不了,只能去控制台看额度。
问题背景
用 Claude Code 跑长任务,或者接的是国内中转站,时不时会看到这样一句:
API Error: Request rejected (429) · Number of request tokens has exceeded your per-minute rate limit
搜这句话的人通常想知道三件事:是谁在限流、Claude Code 自己会不会重试、要等多久。官方文档只说 SDK「会按指数退避重试,遇到 retry-after 头会遵守」,没说 Claude Code 具体重试几次、等多久,也没说 retry-after 很大时会怎样。
站内 上一篇 已经测过 429 的基本重试曲线,但留了几个空白:429 之后恢复成功的过程、交互模式下的显示、retry-after 取较大值时的行为、中转站的 429 长什么样。这篇专门把这些补齐。
问题分析
先确定 429 可能来自哪里,以及它们分别长什么样。下面都来自一手源:
| 来源 | 429 长什么样 | 出处 |
|---|---|---|
| Anthropic 官方 API | {"type":"error","error":{"type":"rate_limit_error","message":"…"}},通常带 retry-after |
官方错误码文档 |
| Anthropic 月度消费上限 | 同样是 rate_limit_error,但不带 retry-after,额度恢复前一直失败 |
同上,原文:A tier spend-cap 429 has no retry-after header and keeps failing until access resumes |
| new-api 按模型限流 | OpenAI 风格:{"error":{"message":"您已达到请求数限制:N分钟内最多请求M次 (request id: …)","type":"new_api_error","code":""}} |
new-api 源码 middleware/model-rate-limit.go、middleware/utils.go(commit 1a4166d8e8) |
| new-api 全局限流 | 空 body,只有 Retry-After 头,值是整个限流窗口的秒数 |
同上 middleware/rate-limit.go 的 writeRateLimited |
nginx limit_req |
一段 HTML 错误页 <title>429 Too Many Requests</title> |
nginx 默认错误页 |
| DeepSeek | 文档只写了 429 - Rate Limit Reached,没给 body 格式 |
DeepSeek 错误码 |
另外,在 2.1.285 的二进制里能找到 anthropic-ratelimit-unified-status、anthropic-ratelimit-unified-reset 等一组响应头名,以及 Usage limit reached 等文案。这组头是订阅用户(Pro / Max)的用量限制,后面会验证:用 API key 接入时它们不生效。
技术方案与选型
| 方案 | 结论 | 理由 |
|---|---|---|
| 本地 stub 模拟后端 | ✅ 采用 | 状态码、body、retry-after 都能精确控制;每个请求带毫秒时间戳记录下来;不花钱,不影响真实账号 |
| 用真实账号撞限流 | ❌ 排除 | 429 很难稳定触发,retry-after 的值也控制不了;还会消耗真实额度,同一账号上的其他服务可能被连带限流 |
| 只读二进制里的字符串 | ❌ 不能单独用 | 文案被编译拆成了碎片,拼不出完整的显示模板;能说明「有这句话」,说明不了「什么时候显示」 |
需要说清楚的一点:服务端返回什么是我们模拟的,Claude Code 客户端怎么反应(重试几次、等多久、显示什么)是真实的。 body 文案尽量照搬上表的一手源。
隔离做法和上一篇相同:
- 每次运行用
env -i清空环境,配一个全新的空CLAUDE_CONFIG_DIR,不碰本机的登录态和会话记录。 ANTHROPIC_BASE_URL指向127.0.0.1上的 stub,ANTHROPIC_API_KEY用一把假 key。HTTPS_PROXY也指向 stub:所有CONNECT外连都会被记录并拒绝。本轮记到的外连目标有 api.anthropic.com、github.com、raw.githubusercontent.com、downloads.claude.ai、registry.npmmirror.com,全部被拦,没有一个请求到达真实服务。-p模式统一用claude -p 'reply with exactly OK' --model haiku < /dev/null;交互模式在 tmux 里启动真实 TUI,每秒截一帧屏幕。
实测过程
本轮共 33 次 claude -p 运行加 3 个交互会话,每个 -p case 至少跑 2 次,报错文本、退出码、请求数全部一致。
1. 不同来源的 429,Claude Code 显示什么
设 CLAUDE_CODE_MAX_RETRIES=0 关掉重试,直接看最终报错(stdout 原文,exit 均为 1,stderr 均为 0 字节):
| 后端返回 | Claude Code 显示 |
|---|---|
Anthropic 格式 rate_limit_error |
API Error: Request rejected (429) · Number of request tokens has exceeded your per-minute rate limit |
| new-api 按模型限流 | API Error: Request rejected (429) · 您已达到请求数限制:1分钟内最多请求10次 (request id: 2026…) |
| new-api 全局限流(空 body) | API Error: Request rejected (429) · status code (no body) |
| nginx HTML 错误页 | API Error: Request rejected (429) · Too Many Requests |
Anthropic 格式 + 订阅限流头 anthropic-ratelimit-unified-status: rejected |
和第一行完全相同,订阅头被忽略 |
所以判断「谁限的流」看 · 后面:英文 rate limit 字样多半是官方或兼容官方格式的网关,中文提示是中转站,status code (no body) 是网关层面的全局限流,Too Many Requests 是前面的 nginx。
2. 会重试多久
默认设置下,429 一直不恢复(模拟月度上限那种不带 retry-after 的 429):
| case | stub 请求数 | 墙钟 | 相邻请求间隔(ms) |
|---|---|---|---|
不带 retry-after,第 1 次运行 |
11 | 182.30 s | 618, 1118, 2129, 4920, 9818, 16225, 35616, 35118, 36829, 39009 |
| 同上,第 2 次运行 | 11 | 176.41 s | 536, 1093, 2371, 4760, 9361, 16311, 32934, 34650, 38356, 35746 |
| 带订阅限流头 × 2 次 | 11 / 11 | 175.94 / 176.52 s | 同一条曲线 |
1 次原始请求加 10 次重试,间隔从 0.5 秒起逐次翻倍,从第 7 次起封顶在 32~40 秒,总共约 3 分钟。这和上一篇在 2.1.280 上测到的曲线一致,两个版本之间没有变化。
3. 中途恢复:完全无感
前 3 次返回 429,第 4 次返回正常:
| case | exit | 墙钟 | 间隔(ms) | stdout |
|---|---|---|---|---|
429 带 retry-after: 1 ×3 → 正常 |
0 / 0 | 5.06 / 4.45 s | 1024, 1015, 2187 / 1011, 1061, 2110 | OK |
429 不带 retry-after ×3 → 正常 |
0 / 0 | 4.73 / 4.23 s | 513, 1225, 2129 / 573, 1260, 2108 | OK |
-p 模式下恢复后 stdout 只有 OK,stderr 是 0 字节,exit 0。脚本完全看不出中途被限流过,只是慢了几秒。
注意第一行的第 3 个间隔是 2.1 秒而不是 1 秒:retry-after: 1 时,实际等待看起来是「retry-after 和指数退避两者取大」。这是从读数推断的,没有在代码里确认。
4. retry-after 的 60 秒上限
这是本次最重要的发现。429 带不同的 retry-after,下一次返回正常:
retry-after |
结果 | stub 请求数 | 墙钟 |
|---|---|---|---|
| 10 | ✅ 等满后恢复,exit 0 | 2 / 2 | 10.88 / 10.31 s(间隔 10018 / 10012 ms) |
| 60 | ✅ 等满后恢复,exit 0 | 2 / 2 | 60.87 / 60.31 s(间隔 60022 / 60012 ms) |
| 61 | ❌ 不重试,直接报错,exit 1 | 1 / 1 / 1 | 0.55 / 0.27 / 0.27 s |
| 90、120、180、300、301、360 | ❌ 同上 | 各 1 | 0.53~0.59 s |
| 600 | ❌ 同上 | 1 / 1 | 0.88 / 0.45 s |
**retry-after ≤ 60 秒会老老实实等满,≥ 61 秒一次都不重试。**交互模式下也一样,retry-after: 61 时界面上 0 秒就显示了最终报错(Churned for 0s)。
这对用中转站的人影响很大:new-api 的全局限流会把整个窗口的秒数放进 Retry-After,窗口一旦超过 1 分钟,Claude Code 就会立刻放弃,看起来像「一点就报错,根本没重试」。
我在二进制里查了所有带 RETRY 的环境变量名,跟重试有关的只有 CLAUDE_CODE_MAX_RETRIES(控制次数),没找到能调这个 60 秒上限的开关。
5. 交互模式:界面长什么样
在 tmux 里启动真实的交互式会话,发一句话,后端一直返回 429(retry-after: 1),每秒截一帧。按时间顺序,提示行依次是:
✻ API error · Retrying in 1s · attempt 2/10
✻ 429 Number of request tokens has exceeded your per-minute rate limit · Retrying in 3s · attempt 3/10
✻ 429 Number of request tokens has exceeded your per-minute rate limit · Retrying in 9s · attempt 5/10
✻ 429 Number of request tokens has exceeded your per-minute rate limit · Retrying in 39s · attempt 7/10
…
⏺ API Error: Request rejected (429) · Number of request tokens has exceeded your per-minute rate limit
✻ Brewed for 3m 3s
- 第 2 次尝试只显示笼统的
API error,从第 3 次起才带上429和后端原话。 Retrying in Ns每秒刷新倒计时,所以「界面一直在动」不是卡死。- 最终报错之后,会话不会退出,可以直接再发一句。
另一个会话里,前几次返回 429,之后恢复正常:界面上只闪过一下 API error · Retrying in 2s · attempt 2/10,然后正常输出 OK,对话记录里不留任何报错痕迹。
6. 交互模式下,一句话会发 2 个请求
看 stub 日志才发现:交互模式下每发一句话,会同时发出 2 个 /v1/messages 请求。一个带 28 个工具定义,是主请求;另一个 0 个工具,内容以 <session> 开头,是一个附带的侧请求。两路各自独立重试。
| 时间(s) | 主请求 | 侧请求 |
|---|---|---|
| 6.08 / 6.09 | 429 | 429 |
| 8.09 / 8.10 | 429 | 429 |
| 10.10 / 10.10 | 正常 | 正常 |
对按「每分钟请求数」限流的中转站来说,交互模式消耗的请求数是你以为的 2 倍;被限流后两路同时重试,也是 2 倍。-p 模式只发 1 个请求(上面所有 -p case 的请求数都能对上)。
实践效果
**1. 先看 · 后面是谁说的。**英文 rate limit 是官方或兼容网关;中文「您已达到请求数限制」是中转站,去中转站后台调限额或换分组;status code (no body) 是网关的全局限流;Too Many Requests 是前置 nginx。
**2. 秒报错、没等待,多半是 retry-after 超过了 60 秒。**可以用下面这条命令看后端到底返回了多大的 retry-after(地址和 key 取自你给 Claude Code 配的环境变量,模型名换成你的后端支持的):
curl -s -o /dev/null -D - "$ANTHROPIC_BASE_URL/v1/messages" \
-H "x-api-key: $ANTHROPIC_API_KEY" -H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"claude-haiku-4-5","max_tokens":8,"messages":[{"role":"user","content":"hi"}]}' \
| grep -iE '^HTTP|retry-after'
注意这条命令本身也会消耗一次请求额度。如果看到 retry-after 大于 60,等这么久再发,或者让中转站把限流窗口调小。
**3. 脚本 / CI 里:**429 能在 3 分钟内恢复的话,-p 的输出和退出码完全正常,不需要额外处理。如果想快速失败(比如健康检查),用 CLAUDE_CODE_MAX_RETRIES=0。长任务不要设成 0,429 本来就该退避重试。
**4. 中转站运营方:**想让 Claude Code 自动等待,Retry-After 不要超过 60;超过就会被当成「不值得等」直接失败。另外,交互模式下每句话会打 2 个请求,按请求数限流时要算进去。
**5. 月度消费上限:**官方的这类 429 不带 retry-after,Claude Code 会白白重试 3 分钟后报错。重试解决不了,去 Console 看额度或调整 spend limit。
本轮全部请求都打在本地 stub 上,真实 API 花费为 0。
踩坑
- **看门狗的
sleep进程占住了管道。**runner 用一个后台子 shell 做超时看门狗,正常结束时 kill 掉子 shell,但里面的sleep 900成了孤儿进程,继续占着 stdout。结果是写文件时没有问题,接| grep时就卡 15 分钟。改成在子 shell 里exec >/dev/null并用trap带走sleep后解决。 - **交互模式下 429 序列被两路请求瓜分。**第一次跑恢复 case 时,按「前 4 次返回 429」设计,结果比预期快了一半,看日志才发现主请求和侧请求各吃掉了 2 次。上面第 6 节就是这么来的。
- 二进制里的文案是碎片。
Usage limit reached、Retrying in、too far out to wait for都能搜到,但拼不出完整句子,最终以实测截屏为准。
未验证清单:真实 Anthropic 后端的 429 body 原文(本文用的是上一篇沿用的文案);DeepSeek 429 的 body 格式(文档未给出);OAuth 订阅登录(Pro / Max)下的用量限制界面(本文只测了 API key 模式,订阅头在该模式下被忽略);retry-after 在 5960 秒之间的边界抖动;3 次。retry-after 用 HTTP 日期格式而不是秒数时的行为。每个 -p case 只跑了 2