You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
用 Claude Code 作为 provider 时频繁出现 "rate limit" 打断 agent turn。用户去查 Claude Code 的 usage 页面,完全没有到限额 —— 因此以为是 NarraNexus 误报。
实测从本地 DB 挖出原始错误(events.event_log),429 是真的,但它不是用户以为的那种 rate limit:
due to an API error: You've hit your session limit · resets 3:20am (America/New_York)
(error type rate_limit, HTTP 429, request id req_011CeYACYtmED5ZgqkdBEwvY,
model sent to the API: claude-opus-5)
Everything below is PARTIAL output recovered from the agent before ...
结果:模型一次 turn 里想扇出多少研究子代理就扇出多少,每个都是独立的 API 流、都在烧同一个 5 小时 session 预算,而且用的是 claude-opus-5。窗口被一次并行调研打穿,正是用户"明明没怎么用却频繁撞限"的体感来源。
5. 已完成的部分成果被丢弃
Claude Code 自己回收了部分输出(错误文本里的 Everything below is PARTIAL output recovered from the agent before ...),但 NarraNexus 既没有把这一轮标记为 incomplete,也没有提供 resume/续跑入口。多个子代理已经交付的中间成果就这么没了。
Steps to reproduce
dev 分支(本次为 c776e32a / v1.15.0),provider 选 Claude Code,模型 claude-opus-5
给 agent 一个需要大量并行调研的任务,让它扇出多个研究子代理
跑几轮,直到 Anthropic 5 小时 session 额度耗尽
观察:agent 用自然语言说"被 API 速率限制打断了",turn 以残缺输出结束;无重试、无恢复时间提示、无续跑入口
... due to an API error: You've hit your session limit · resets 3:20am (America/New_York)
(error type rate_limit, HTTP 429, request id req_011CeYACYtmED5ZgqkdBEwvY, model sent to the API: claude-opus-5)
... (同上, request id req_011CeYACZAe7JfiKbEFyypDp)
... (同上, request id req_011CeYACaqr4T6S5f6BGbRVi)
用户侧看到的(agent 自然语言转述,非平台提示):
这轮调研我进行了大量并行研究,但中途被 API 速率限制打断了——多个研究代理提交了部分成果后就断了。
What happened?
用 Claude Code 作为 provider 时频繁出现 "rate limit" 打断 agent turn。用户去查 Claude Code 的 usage 页面,完全没有到限额 —— 因此以为是 NarraNexus 误报。
实测从本地 DB 挖出原始错误(
events.event_log),429 是真的,但它不是用户以为的那种 rate limit:这是 Claude Code 的 5 小时 session 额度,Anthropic 用
error type rate_limit / HTTP 429的外壳返回 —— 和 usage 页面展示的不是同一个计量,所以用户查不到。NarraNexus 没有误报。真正的缺陷在于:这个错误进来之后,平台既不重试、也不告诉用户发生了什么、还丢掉了已完成的工作。同一次 turn 里有 3 个不同 request id 连续撞上(多个研究子代理并发),用户看到的只是 agent 用自然语言转述一句"被 API 速率限制打断了"。
仓库核对(dev @
c776e32a/ v1.15.0):1. 归类为 TRANSIENT,然后没有任何东西重试它
loop/circuit_breaker.py:99-104的_TRANSIENT_MARKERS含"rate limit"/"429"→classify_agent_error(:131-176)返回ErrorCategory.TRANSIENT。TRANSIENT ∉
PAUSING_CATEGORIES(schema/agent_circuit_breaker_schema.py:67-69只含 AUTH/QUOTA),所以不会熔断 —— 这点是对的,与 #117 无关。但 grep 全仓
TRANSIENT的消费方只有wechat_trigger/model_sync/logging/_ship.py,agent turn 路径一个都没有。也就是说:系统明确判定"这是暂时性的、重试可恢复",然后从不重试,turn 直接以残缺输出结束。2. 错误里带着 reset 时间,但被丢弃
原文含机器可读的
resets 3:20am (America/New_York)。平台没有解析它,因此无法做退避重试、无法排程恢复、也无法在 UI 上告诉用户"几点恢复"。3.
failure.py对 session limit 零识别 → 用户拿不到可操作指引agent_framework/llm/failure.py的自助错误分类(SELF_SERVICEABLE_REASON_*)里 没有 rate_limit / session limit 这一类(grepsession limit|rate_limit|429|resets= 0 命中)。所以classify_self_serviceable返回None,用户得不到类似 "已用完 5 小时 session 额度,3:20am 恢复" 的明确提示 —— 只能看 agent 的自然语言转述,然后去 usage 页面白查一场。对比:同文件对
insufficient_balance/free_tier_exhausted/context_window/model_not_found都有精确 marker + 可操作文案。session limit 是 Claude Code provider 下最常撞的一种,反而缺席。4. 子代理扇出无并发上限 —— 这是放大器
adapters/claude/sdk.py:876-925:permission_mode="bypassPermissions"、max_turns=None、只在条件成立时禁WebSearch,没有agents=配置。grep
semaphore|max_concurrent|concurrency在整个adapters/claude/+loop/路径下 零命中;grep429|backoff|RateLimit在adapters/claude/下也只有 stale-resume 的 cold retry(sdk.py:1293-1363,明确限定"至多一次、且只在有输出之前",不针对 429)。结果:模型一次 turn 里想扇出多少研究子代理就扇出多少,每个都是独立的 API 流、都在烧同一个 5 小时 session 预算,而且用的是
claude-opus-5。窗口被一次并行调研打穿,正是用户"明明没怎么用却频繁撞限"的体感来源。5. 已完成的部分成果被丢弃
Claude Code 自己回收了部分输出(错误文本里的
Everything below is PARTIAL output recovered from the agent before ...),但 NarraNexus 既没有把这一轮标记为 incomplete,也没有提供 resume/续跑入口。多个子代理已经交付的中间成果就这么没了。Steps to reproduce
c776e32a/ v1.15.0),provider 选 Claude Code,模型claude-opus-5NarraNexus version
v1.15.0(dev @
c776e32a)How are you running NarraNexus?
本地源码 /
bash run.sh(macOS)Logs / screenshots
原始错误取自本地
~/.narranexus/nexus.db的events.event_log(event 时间2026-08-30 02:21:49,agent_00f08eaaef23),同一 turn 内三条:用户侧看到的(agent 自然语言转述,非平台提示):
Anything else?
建议修复(按性价比排序):
给 session-limit 429 一个专属分类 + 可操作文案。 在
failure.py加SELF_SERVICEABLE_REASON_SESSION_LIMIT,marker 用"session limit"(配合error type rate_limit),文案直接把错误里的 reset 时间透出来:"Claude Code 5 小时 session 额度已用完, 恢复"。这样用户不会再去 usage 页面白查。注意别撞
classify_agent_error的既有排序陷阱 —— 该函数 docstring 明确说 QUOTA 分支刻意不看"429"/"rate limit",新增 reason 要保持这条不变(session limit 不是余额问题,remedy 是等待而非充值)。让 TRANSIENT 在 agent turn 路径上真的有人管。 现在"判定为可重试"和"实际重试"之间是断的。至少解析 reset 时间做一次排程续跑;铁律 weekly #14 下不要做无上限轮询,按 reset 时间单次唤醒即可。
给子代理扇出加并发上限。 这是根治"窗口被一次并行调研打穿"的地方。可以是固定上限,也可以按 provider 区分(Claude Code 的 session 预算比 API key 计费更容易打穿)。
保住残缺成果。 Claude Code 已经把 partial output 交回来了,把这一轮标记为 incomplete 并给个续跑入口,别让多个子代理的中间成果白跑。
关联: