在 AI Agent 编程工具的日常使用中,限流(Rate Limiting) 是开发者最常遇到的"拦路虎"之一。Claude Code 作为 Anthropic 官方推出的命令行 AI 编程助手,其内部构建了一套极为精密的限流与速率限制系统。本文将深入源码,解析 Claude Code 如何处理 API 配额、模拟限流场景、实现智能重试以及模型降级策略,帮助读者理解一个生产级 AI 工具如何在资源受限的边界内保持优雅的用户体验。
一、限流架构概览
Claude Code 的限流系统分布在三个核心文件中:
src/services/claudeAiLimits.ts(约 16KB):限流状态的核心逻辑,负责解析 API 响应头、计算配额状态、触发早期预警。src/services/mockRateLimits.ts(约 29KB):内部测试用的 mock 限流系统,支持 20 多种限流场景的模拟。src/services/rateLimitMessages.ts:限流消息的集中管理,统一生成错误提示和警告文案。
flowchart TD
A[API 响应] --> B[processRateLimitHeaders]
B --> C{是否启用 mock?}
C -->|是| D[applyMockHeaders]
C -->|否| E[原始 Headers]
D --> F[computeNewLimitsFromHeaders]
E --> F
F --> G[extractQuotaStatusFromHeaders]
G --> H[emitStatusChange]
H --> I[UI 渲染层]
H --> J[Retry 决策层]上图展示了限流数据从 API 响应到 UI 展示的完整链路。值得注意的是,Claude Code 不在客户端做任何封号判断,所有限制均通过服务端下发的 anthropic-ratelimit-unified-* 响应头传递,客户端只是忠实执行(src/services/claudeAiLimits.ts,第 1~15 行)。这种设计将策略决策完全交由服务端,确保客户端逻辑简洁且不会过时。
二、速率限制类型
Anthropic 的 API 采用多层级的统一限流器(Unified Rate Limiter),Claude Code 在其基础上封装了五种核心限流类型:
2.1 五种核心限流窗口
// src/services/claudeAiLimits.ts,第 27~36 行
type QuotaStatus = 'allowed' | 'allowed_warning' | 'rejected'
type RateLimitType =
| 'five_hour' // 5 小时滑动窗口(会话级限制)
| 'seven_day' // 7 天滚动窗口(周级限制)
| 'seven_day_opus' // Opus 模型专用的 7 天限制
| 'seven_day_sonnet' // Sonnet 模型专用的 7 天限制
| 'overage' // 超额使用限制(付费后仍有上限)每种限流类型对应不同的用户行为和业务场景:
| 限流类型 | 时间窗口 | 触发场景 | 重置策略 |
|---|---|---|---|
five_hour | 5 小时滑动窗口 | 单一会话内过度使用 | 从首次请求开始计时 |
seven_day | 7 天滚动窗口 | 周度总量超标 | 自然滚动重置 |
seven_day_opus | 7 天(Opus 专属) | Opus 模型消耗过快 | 与周窗口同步 |
seven_day_sonnet | 7 天(Sonnet 专属) | Sonnet 模型消耗过快 | 与周窗口同步 |
overage | 按配置 | 超额额度耗尽 | 按组织账单周期 |
2.2 响应头解析机制
每次 API 调用后,Claude Code 从响应头中提取限流状态(src/services/claudeAiLimits.ts,第 376~436 行):
function computeNewLimitsFromHeaders(headers: globalThis.Headers): ClaudeAILimits {
// 核心状态:allowed / allowed_warning / rejected
const status =
(headers.get('anthropic-ratelimit-unified-status') as QuotaStatus) || 'allowed'
// 重置时间(Unix 秒)
const resetsAtHeader = headers.get('anthropic-ratelimit-unified-reset')
const resetsAt = resetsAtHeader ? Number(resetsAtHeader) : undefined
// 命中的限制类型
const rateLimitType = headers.get(
'anthropic-ratelimit-unified-representative-claim',
) as RateLimitType | null
// 超额使用状态
const overageStatus = headers.get(
'anthropic-ratelimit-unified-overage-status',
) as QuotaStatus | null
// 超额被禁用的原因(12 种可能)
const overageDisabledReason = headers.get(
'anthropic-ratelimit-unified-overage-disabled-reason',
) as OverageDisabledReason | null
// ...
}当 status === 'rejected' 时,所有后续 API 调用会被立即阻止,用户必须等到 resetsAt 时间戳之后才能恢复使用。
2.3 超额禁用原因枚举
Anthropic 定义了 12 种超额使用被禁用的原因(src/services/claudeAiLimits.ts,第 107~120 行):
export type OverageDisabledReason =
| 'overage_not_provisioned' // 未开通超额
| 'org_level_disabled' // 组织级别禁用
| 'org_level_disabled_until' // 组织级别临时禁用
| 'out_of_credits' // 余额不足
| 'seat_tier_level_disabled' // 席位等级禁用
| 'member_level_disabled' // 个人账户禁用
| 'seat_tier_zero_credit_limit' // 席位等级信用额度为零
| 'group_zero_credit_limit' // 组信用额度为零
| 'member_zero_credit_limit' // 个人信用额度为零
| 'org_service_level_disabled' // 组织服务级别禁用
| 'org_service_zero_credit_limit' // 组织服务信用额度为零
| 'no_limits_configured' // 未配置限制
| 'unknown' // 未知原因这种精细化的原因分类让 Claude Code 能够向用户展示准确的引导信息,例如余额不足时提示充值,席位等级禁用时引导联系管理员。
三、Mock 限流系统
对于内部测试和演示场景,Claude Code 提供了一套完整的 Mock 限流系统,仅在 USER_TYPE === 'ant'(Anthropic 内部员工)时激活。
3.1 Mock 场景覆盖
src/services/mockRateLimits.ts 定义了 21 种 mock 场景(第 62~82 行):
export type MockScenario =
| 'normal'
| 'session-limit-reached'
| 'approaching-weekly-limit'
| 'weekly-limit-reached'
| 'overage-active'
| 'overage-warning'
| 'overage-exhausted'
| 'out-of-credits'
| 'org-zero-credit-limit'
| 'org-spend-cap-hit'
| 'member-zero-credit-limit'
| 'seat-tier-zero-credit-limit'
| 'opus-limit'
| 'opus-warning'
| 'sonnet-limit'
| 'sonnet-warning'
| 'fast-mode-limit'
| 'fast-mode-short-limit'
| 'extra-usage-required'
| 'clear'外部构建(external build)中,这些函数被编译为无操作(no-op)的空实现(src/services/mockRateLimits.ts,第 84~128 行),确保生产环境不会意外触发模拟逻辑:
export function setMockHeader(
_key: MockHeaderKey,
_value: string | undefined,
): void {}
export function getMockHeaders(): MockHeaders | null {
return null
}
export function shouldProcessMockLimits(): boolean {
return false
}3.2 Mock 的 facade 隔离
生产代码通过 rateLimitMocking.ts 与 mock 系统解耦(src/services/rateLimitMocking.ts,第 1~27 行):
/**
* Facade for rate limit header processing
* This isolates mock logic from production code
*/
export function processRateLimitHeaders(
headers: globalThis.Headers,
): globalThis.Headers {
// Only apply mocks for Ant employees using /mock-limits command
if (shouldProcessMockLimits()) {
return applyMockHeaders(headers)
}
return headers
}这种 Facade 模式 的设计非常精妙:生产代码始终调用 processRateLimitHeaders(),无需关心底层是真实限流还是 mock 限流。Mock 逻辑被完全隔离在可选模块中,外部构建甚至可以将整个 mock 模块 tree-shake 掉。
3.3 开发调试命令
内部员工可通过 /mock-limits 命令快速切换限流场景,用于验证 UI 提示、重试逻辑和降级策略的正确性。这是大型工程中**可测试性(Testability)**设计的典范——不依赖外部 API 状态,即可在本地复现生产环境的边界条件。
四、早期预警系统
Claude Code 最具工程智慧的设计之一,是在用户真正被限流之前就发出警告。系统通过两种方式实现早期预警:
4.1 服务端 surpassed-threshold 头
当 API 响应包含 anthropic-ratelimit-unified-{claim}-surpassed-threshold 头时,客户端直接展示对应警告(src/services/claudeAiLimits.ts,第 255~294 行)。
4.2 客户端时间相对预警(Fallback)
当服务端未发送阈值头时,Claude Code 会自行计算用户是否在"燃烧"配额(src/services/claudeAiLimits.ts,第 53~70 行):
const EARLY_WARNING_CONFIGS: EarlyWarningConfig[] = [
{
rateLimitType: 'five_hour',
windowSeconds: 5 * 60 * 60,
thresholds: [{ utilization: 0.9, timePct: 0.72 }],
},
{
rateLimitType: 'seven_day',
windowSeconds: 7 * 24 * 60 * 60,
thresholds: [
{ utilization: 0.75, timePct: 0.6 },
{ utilization: 0.5, timePct: 0.35 },
{ utilization: 0.25, timePct: 0.15 },
],
},
]预警逻辑的核心是使用率-时间比(src/services/claudeAiLimits.ts,第 98~103 行):
function computeTimeProgress(resetsAt: number, windowSeconds: number): number {
const nowSeconds = Date.now() / 1000
const windowStart = resetsAt - windowSeconds
const elapsed = nowSeconds - windowStart
return Math.max(0, Math.min(1, elapsed / windowSeconds))
}例如,对于 7 天窗口:如果用户在时间只过去了 35% 的情况下就消耗了 50% 的配额,系统会立即发出警告。这种速度感知的预警比单纯的阈值判断更加智能,因为它考虑到了用户的消耗速率。
五、重试机制
当 API 返回错误时,Claude Code 的重试系统展现了极高的工程成熟度。核心实现在 src/services/api/withRetry.ts 中。
5.1 重试架构
sequenceDiagram
participant U as 用户
participant Q as QueryEngine
participant R as withRetry
participant A as Anthropic API
U->>Q: 发送请求
Q->>R: withRetry(operation)
loop 最多 maxRetries+1 次
R->>A: API 调用
alt 成功
A-->>R: 返回结果
R-->>Q: yield 结果
else 429/529 错误
A-->>R: 返回错误
R->>R: 计算退避延迟
R-->>Q: yield SystemAPIErrorMessage
Q-->>U: 显示"正在重试..."
R->>R: sleep(delayMs)
else 不可重试错误
A-->>R: 返回错误
R-->>Q: throw CannotRetryError
Q-->>U: 显示错误
end
end5.2 指数退避与抖动
重试延迟采用指数退避 + 随机抖动策略(src/services/api/withRetry.ts,第 530~548 行):
export function getRetryDelay(
attempt: number,
retryAfterHeader?: string | null,
maxDelayMs = 32000,
): number {
if (retryAfterHeader) {
const seconds = parseInt(retryAfterHeader, 10)
if (!isNaN(seconds)) {
return seconds * 1000
}
}
const baseDelay = Math.min(
BASE_DELAY_MS * Math.pow(2, attempt - 1),
maxDelayMs,
)
const jitter = Math.random() * 0.25 * baseDelay
return baseDelay + jitter
}参数设计:
BASE_DELAY_MS = 500:基础延迟 500msmaxDelayMs = 32000:最大延迟 32 秒jitter = 25%:随机抖动避免惊群效应
如果 API 返回 Retry-After 头,则优先使用服务端指定的时间。
5.3 最大重试次数
默认最大重试次数为 10 次,但可通过环境变量覆盖(src/services/api/withRetry.ts,第 52~53 行、第 789~794 行):
const DEFAULT_MAX_RETRIES = 10
export function getDefaultMaxRetries(): number {
if (process.env.CLAUDE_CODE_MAX_RETRIES) {
return parseInt(process.env.CLAUDE_CODE_MAX_RETRIES, 10)
}
return DEFAULT_MAX_RETRIES
}5.4 错误分类与决策矩阵
shouldRetry() 函数实现了精细化的错误分类(src/services/api/withRetry.ts,第 696~787 行):
function shouldRetry(error: APIError): boolean {
// Never retry mock errors
if (isMockRateLimitError(error)) {
return false
}
// Persistent mode: 429/529 always retryable
if (isPersistentRetryEnabled() && isTransientCapacityError(error)) {
return true
}
// CCR mode: 401/403 are transient blips
if (
isEnvTruthy(process.env.CLAUDE_CODE_REMOTE) &&
(error.status === 401 || error.status === 403)
) {
return true
}
// Check x-should-retry header
const shouldRetryHeader = error.headers?.get('x-should-retry')
if (shouldRetryHeader === 'true') {
return true
}
// Retry on connection errors
if (error instanceof APIConnectionError) {
return true
}
// 429 rate limits: retry for non-subscribers or enterprise
if (error.status === 429) {
return !isClaudeAISubscriber() || isEnterpriseSubscriber()
}
// 401: clear cache and retry
if (error.status === 401) {
clearApiKeyHelperCache()
return true
}
// 5xx: retry server errors
if (error.status && error.status >= 500) return true
return false
}关键决策逻辑:
- Mock 错误:永不重试,避免测试死循环
- 429 限流:Claude.ai 订阅用户(Pro/Max)不重试,因为窗口重置时间通常很长;企业用户和 API Key 用户则重试
- 529 过载:区分前台/后台请求,前台请求重试(用户正在等待),后台请求直接丢弃(避免级联放大)
- 401 认证错误:清除缓存后重试,触发 OAuth Token 刷新
5.5 持久化重试模式(Persistent Retry)
对于无人值守会话(unattended sessions),Claude Code 提供了一种特殊的持久化重试模式(src/services/api/withRetry.ts,第 91~104 行):
const PERSISTENT_MAX_BACKOFF_MS = 5 * 60 * 1000 // 5 分钟
const PERSISTENT_RESET_CAP_MS = 6 * 60 * 60 * 1000 // 6 小时
const HEARTBEAT_INTERVAL_MS = 30_000 // 30 秒在此模式下,429/529 错误会无限重试,退避上限为 5 分钟,且每次长等待期间会定期 yield 心跳消息,防止宿主环境将空闲会话标记为死亡。这对于 CI/CD 集成或长时间后台任务至关重要。
六、降级策略
当资源受限时,Claude Code 不会简单粗暴地报错退出,而是实施多层降级策略,尽可能保持核心功能可用。
6.1 模型降级:Opus → Sonnet → Haiku
当连续遭遇 529 过载错误(默认阈值 3 次)时,系统会触发模型降级(src/services/api/withRetry.ts,第 326~364 行):
if (is529Error(error)) {
consecutive529Errors++
if (consecutive529Errors >= MAX_529_RETRIES) {
// Check if fallback model is specified
if (options.fallbackModel) {
logEvent('tengu_api_opus_fallback_triggered', {
original_model: options.model,
fallback_model: options.fallbackModel,
})
// Throw special error to indicate fallback was triggered
throw new FallbackTriggeredError(
options.model,
options.fallbackModel,
)
}
}
}降级链路通常遵循:
Claude Opus(最强推理)
↓ 连续 3 次 529
Claude Sonnet(均衡性能)
↓ 继续受限
Claude Haiku(最快最轻)FallbackTriggeredError 被上游 QueryEngine 捕获后,会以降级后的模型重新发起请求,用户几乎无感知。
6.2 Fast Mode 降级
Fast Mode 是 Claude Code 的一项加速功能,但在限流时会自动降级为标准速度(src/services/api/withRetry.ts,第 261~305 行):
if (wasFastModeActive && error instanceof APIError &&
(error.status === 429 || is529Error(error))) {
const retryAfterMs = getRetryAfterMs(error)
if (retryAfterMs !== null && retryAfterMs < SHORT_RETRY_THRESHOLD_MS) {
// 短等待:保持 Fast Mode 以保留 Prompt Cache
await sleep(retryAfterMs, options.signal, { abortError })
continue
}
// 长等待:进入 Cooldown,切换到标准速度
const cooldownMs = Math.max(
retryAfterMs ?? DEFAULT_FAST_MODE_FALLBACK_HOLD_MS,
MIN_COOLDOWN_MS,
)
triggerFastModeCooldown(Date.now() + cooldownMs, cooldownReason)
retryContext.fastMode = false
continue
}这里有两个关键设计:
- 短等待(<20秒):保持 Fast Mode 活跃,因为重新启用需要重建 Prompt Cache,成本较高
- 长等待(≥20秒):主动降级到标准速度,避免在等待期间占用 Fast Mode 配额
6.3 功能降级与提示引导
当配额完全耗尽时,Claude Code 通过 rateLimitMessages.ts 向用户展示精准的状态信息和操作引导(src/services/rateLimitMessages.ts,第 45~104 行):
export function getRateLimitMessage(
limits: ClaudeAILimits,
model: string,
): RateLimitMessage | null {
// 超额使用场景
if (limits.isUsingOverage) {
if (limits.overageStatus === 'allowed_warning') {
return {
message: "You're close to your extra usage spending limit",
severity: 'warning',
}
}
return null
}
// 被拒绝状态
if (limits.status === 'rejected') {
return { message: getLimitReachedText(limits, model), severity: 'error' }
}
// 预警状态(利用率 > 70% 才显示)
if (limits.status === 'allowed_warning') {
const WARNING_THRESHOLD = 0.7
if (limits.utilization !== undefined && limits.utilization < WARNING_THRESHOLD) {
return null
}
const text = getEarlyWarningText(limits)
if (text) {
return { message: text, severity: 'warning' }
}
}
return null
}消息文案会根据订阅类型动态调整 upsell 引导(src/services/rateLimitMessages.ts,第 261~297 行):
- Pro/Max 用户:提示
/upgrade to keep using Claude Code - Team/Enterprise 用户:提示
/extra-usage to request more - 已启用超额的用户:静默无缝切换,不打扰用户
6.4 预飞检查(Pre-flight Check)
每次会话启动时,Claude Code 会发送一个极小的探测请求来预检配额状态(src/services/claudeAiLimits.ts,第 199~218 行):
async function makeTestQuery() {
const model = getSmallFastModel() // 使用最便宜的 Haiku 模型
const anthropic = await getAnthropicClient({
maxRetries: 0,
model,
source: 'quota_check',
})
return anthropic.beta.messages.create({
model,
max_tokens: 1, // 只请求 1 个 token
messages: [{ role: 'user', content: 'quota' }],
}).asResponse()
}这个设计体现了极致的成本意识:用最便宜的模型 + 最少的 token 完成探测,只为获取响应头中的配额信息,而不消耗宝贵的会话配额。
七、限流消息渲染
限流消息的最终呈现由 RateLimitMessage.tsx 组件负责,而文案统一由 rateLimitMessages.ts 生成。这种文案与渲染分离的架构确保了:
- 文案可集中管理,避免碎片化
- UI 组件只负责展示,不涉及业务逻辑
- 便于国际化(虽然 Claude Code 目前主要支持英文)
错误消息前缀被提取为常量数组(src/services/rateLimitMessages.ts,第 21~27 行):
export const RATE_LIMIT_ERROR_PREFIXES = [
"You've hit your",
"You've used",
"You're now using extra usage",
"You're close to",
"You're out of extra usage",
] as const配合 isRateLimitErrorMessage() 工具函数,下游组件可以快速判断一条消息是否属于限流错误,而无需脆弱的字符串匹配。
八、工程启示
Claude Code 的限流系统为我们设计生产级 AI Agent 提供了以下宝贵经验:
1. 服务端主导策略
所有限流判断由服务端响应头驱动,客户端零策略硬编码。这使得限流规则可以动态调整,无需发版即可生效。
2. 分层防御体系
从早期预警 → 软限制(warning)→ 硬限制(rejected)→ 超额切换 → 模型降级,形成完整的防御梯度,而非简单粗暴地"一刀切"。
3. 可测试性设计
Mock 限流系统让开发者和 QA 可以在本地复现所有生产限流场景,无需等待真实配额耗尽。Facade 隔离确保了 mock 代码不会污染生产逻辑。
4. 用户感知的优雅降级
每次降级都伴随清晰的状态提示和操作引导(/upgrade、/extra-usage),将负面体验转化为商业转化机会。
5. 成本极致优化
预飞检查使用 Haiku + 1 token,重试时区分前台/后台请求,Fast Mode 降级保留 Prompt Cache——每一处细节都体现了对 API 成本的精打细算。
结语
Claude Code 的限流系统不是一个简单的"收到 429 就报错"的粗糙实现,而是一套融合了状态机、预测模型、重试策略、降级链路和商业逻辑的精密工程体系。对于正在构建 AI Agent 应用的开发者而言,这套系统提供了极佳的参考范本:如何在资源受限的现实世界中,既保护后端服务,又最大化用户体验。
理解这些机制后,当你下次在终端里看到 "You’ve used 75% of your weekly limit" 的提示时,你会知道在这行文字背后,是数百行精心设计的代码在为你的编程体验保驾护航。