这周我的Claude Code配额掉得特别快,掐指一算不到七天烧掉了将近一半。起初我以为是代码生成用得太猛,但看了用量明细后总觉得不对——请求次数没涨多少,输入token却翻了几倍。我干脆把Claude Code的本地日志翻出来,顺手做了个逆向工程,逐个请求拆它的上下文,结果发现这个号称能自主处理任务的Agent,在测试层面藏着一个很要命的盲区:所有常规测试都在验证“功能对不对”,没人验证“成本受不受控”。
这篇文章就是我的排查过程和思路复盘。我会先讲Claude Code配额爆炸的具体现象,再聊逆向工程中看到的几个关键事实,然后系统拆解Agent测试的致命盲区,最后给出我总结的一套更务实的测试方案。无论你是在做Agent开发、AI自动化测试,还是单纯想把Claude Code用得省一点,都应该能从中找到直接能用的东西。
1. 项目背景:Claude Code接入一周,配额为什么先崩了
1.1 我最初的使用方式与预期
我大概三个月前开始把Claude Code接入日常开发流程。主要用途比较常规:生成单元测试、做代码审查、处理一些机械性的重构任务,偶尔让它修复CI里的报错。按照我的设想,每天大概发起20到30轮会话,每轮对话的上下文控制在5000到15000个token以内,一个月下来配额应该是比较宽裕的。
实际用起来也确实顺手,代码生成质量不错,尤其是配合工具调用做项目级修改时,它会把多个文件一次性改完。但问题也出在这里——功能越顺手,我越没有去细看背后的调用模型。等到第二周打开配额页面,我发现消耗速度远超预期,这才开始认真对待这件事。
1.2 配额断崖与可疑的调用记录
第一周的账单里,输入token占了总消耗的八成以上,输出token反而不多。这本身就有点反常。正常写代码的对话应该是输出占一定比例,输入再多也不至于压过输出这么多。
我调出Claude Code自带的用量统计,又看了命令行运行日志,发现几个可疑点。一是单轮会话的上下文增长速度非常快,明明只聊了十几轮,日志里的累计token数却到了好几万。二是工具调用频繁,每做完一步操作都会把工具返回结果原封不动塞进下一轮请求。三是系统里出现过多次重试记录,有些重试间隔很短,明显不是人为操作。
我当时看到CLI里弹出一行提示,大意是本周Claude Code的使用额度已经超过50%,建议等limit刷新。这个提示本身没问题,但它让我意识到:我根本不知道每个操作到底花了多少钱、消耗了多少配额。于是我开始考虑,能不能直接通过逆向工程的手段,把Claude Code每次请求的构成拆开看。
1.3 为什么说这是一个测试问题
可能有朋友觉得,配额超了是我用法的问题,关测试什么事。但恰恰相反,我在后面分析中发现,Claude Code这类Agent应用的消耗失控,本质上是因为它的行为链路在发布前没有被充分测试过。
传统软件测试关心的是输入输出对不对、边界条件处理得怎么样、性能有没有达标。但对于Agent应用,一个功能完全正确、处理逻辑没有bug的任务流,也可能因为上下文管理不当、工具调用过密、重试策略失控,造成几十倍的资源消耗差距。换句话说,你测不出来的不只是错误,还有浪费。这正是我要展开讲的核心。
2. 逆向工程分析思路与工具链选型
2.1 为什么选择逆向工程而不是只看文档
有人会问,Claude Code是商业产品,官方文档写得挺清楚,为什么要逆向?我在排查中发现,文档只能告诉你“这个功能能做什么”,不会告诉你“这个功能在什么场景下会发起哪些请求、请求体长什么样、失败时怎么重试”。尤其是Agent行为高度依赖上下文和工具调用结果,光是看文档无法解释我观察到的异常消耗。
做逆向工程,是为了拿到第一手的行为证据。我需要在真实环境里拦截Claude Code发出的网络请求,解析它的上下文结构,再结合进程行为还原出它的决策链路。这就好比调试一个不给你源码的第三方库,你不把它的调用栈拉出来,永远不知道它在背地里做了什么。
2.2 工具链与基本步骤
我用的工具很常规,一套组合拳下来基本能把CLI工具的行为看穿。
首先在本地起一个HTTP代理,我用的是mitmproxy,也可以用Charles或Fiddler,个人偏好mitmproxy是因为它是命令行工具,方便脚本化分析。Claude Code支持HTTPS_PROXY环境变量,指向本地代理后,所有网络请求都会被记录下来。注意这里涉及HTTPS流量解密,需要在系统里安装mitmproxy的CA证书,这只针对我自己的开发机,属于本地调试的正常操作。
其次是CLI本身的日志目录,Claude Code会把会话历史、工具调用记录、错误信息写到本地文件夹里。我在macOS上是看~/.claude和~/Library/Logs/claude这类路径,Windows下则对应%USERPROFILE%\.claude。这些日志比网络请求更完整,因为它还记录了会话内部的状态转移。
第三步是用系统级工具观察进程行为,比如macOS上的lsof和fs_usage,Linux上的strace,Windows上的Process Monitor。我主要用lsof看它打开了哪些网络连接和文件句柄,辅助确认请求是同步还是异步发起的。
完整步骤可以这样梳理:
- 设置环境变量
HTTPS_PROXY=http://127.0.0.1:8080,启动Claude Code。 - 打开mitmproxy,确认代理已经捕获到
api.anthropic.com等域名的请求。 - 执行三个典型任务:生成单元测试、做代码审查、调用工具修改文件。
- 保存所有请求和响应,重点关注Authorization头、请求体大小、token用量字段。
- 对照CLI本地日志,逐条还原每个任务的调用时序。
这套流程跑下来,我对Claude Code的行为模型有了一个非常直观的认识。而且我强烈建议大家做类似分析时保留原始日志,后面写测试用例时非常有价值。
2.3 逆向结果中的三个关键事实
分析完抓包数据之后,有三个事实让我印象很深。
第一个是上下文累积。每一轮对话都会把之前的消息历史完整地重新发送一遍,即使那些历史消息和当前任务已经没什么关系。我做过一个实验:连续让Claude Code做十次很小的代码修改,每次只改动一行,结果到第十次时请求体已经包含了前面所有的修改记录和工具返回结果,上下文从最初的几千个token膨胀到了接近两万。这个特性本身不是Bug,但如果没人监控增长曲线,成本就会在不经意间翻倍。
第二个是工具结果原文回传。Claude Code调用工具时,如果工具返回的是一个超长日志或大文件内容,它会把这些内容直接放进下一轮请求的上下文里。我抓到一个调用git diff的例子,diff输出有30多KB,Claude Code一边展示命令结果,一边原样塞给了模型。这导致一次看似简单的git操作,实际消耗的输入token高得离谱。
第三个是自动重试机制。当模型生成的工具调用格式不对、参数校验失败,或者API返回限流错误时,Claude Code会自动重试。但重试不是只重发失败的这一步,而是重新发送整个当前上下文再加新的错误信息。这种重试放大效应,在异常密集的场景下极其致命,后面我会专门讲。
3. 逆向工程揭露的Agent测试致命盲区
3.1 盲区一:传统测试只测功能正确性,不测成本
这是我最想表达的一个点。我们做单元测试和集成测试,验证的是“结果对不对”,但Agent应用里“结果对”和“成本可控”是两回事。
举个例子,你写一个测试用例,让Claude Code执行一次代码重构,断言最终代码能通过编译、单元测试能通过。这样的测试跑通了,但你完全不知道它为了这次重构发送了多少个请求、上下文膨胀到多大、中间失败重试了几次。在传统应用里,一次错误的代价可能是500ms的延迟,在Agent应用里可能就是几百K token的经济损失。
我把这个称为“成本盲区”。功能测试通过率再高,也测不出一个Agent是否在无谓地消耗配额。想要覆盖这个盲区,必须在测试体系里引入成本指标,把每次任务的token消耗、请求次数、平均上下文大小作为一等公民去断言。
当时我把抓包数据整理成一个表格,发现同样是“生成单元测试”这个任务,上下文处理得好时消耗约2万token,处理得差时能到8万token,功能表现却几乎一致。这种巨大的差异,传统测试根本察觉不到。
3.2 盲区二:测试环境与真实模型的上下文差异
做Agent开发时,很多团队会在测试环境里用mock的LLM替代真实模型,以便加快测试速度和降低成本。这个思路本身没错,但它造成了一个很大的盲区:mock LLM的上下文窗口是恒定的,不会膨胀,不会截断,也不会因为消息历史过多发生行为变化。
真实情况是,Claude Code的上下文窗口有限,超过之后会有压缩策略,压缩后模型可能丢失部分细节,进而改变决策行为。而你的mock环境永远模拟不出这种“记忆衰退”。结果就是,在测试环境里一切正常、成本合理,一上真实模型就上下文爆炸、行为漂移。
我在逆向分析中看到过Claude Code处理长会话时的截断逻辑,它会优先丢弃早期消息,保留最近对话和工具结果。这个策略在大多数场景下是正确的,但如果你工具返回的内容特别长,截断就很容易把关键指令挤出去,模型后续就会表现得“前言不搭后语”。
所以,Agent测试不能只在mock环境里跑,必须有一部分用真实模型、真实上下文管理逻辑来做“影子测试”,否则你测的只是自己的mock没问题,而不是Agent没问题。
3.3 盲区三:工具调用图的收敛性没有被验证
Agent和普通函数的区别在于,它可以连续多次调用工具,形成一张动态的调用图。传统测试不关心这张图是否存在环、是否发散、是否会重复执行同一个操作。
逆向分析时我观察到一个典型案例:某个自动化修复任务里,模型反复调用grep搜索同一个关键词,每次搜索的输入参数几乎一致,只是把上次的输出重新包装了一下。这个循环如果没有人干预,可能持续十几轮,每一轮都在消耗配额。单看每一步,都是合法合理的工具调用,连在一起就是一个低效死循环。
我管这叫“调用图收敛性盲区”。一个健康的Agent任务流,工具调用序列应该是朝着完成任务的方向收敛的,不应出现连续相同的调用、无意义的重复读取、或者单路径上的无限递归。测试时应该追踪这些序列,对“同一工具连续调用N次以上”这样的模式设防。
3.4 盲区四:错误处理路径的配额放大效应
第三个关键事实提到过自动重试机制,这里我要单独展开讲。错误处理是传统测试的重点,但Agent应用里的错误处理有一个独特之处——每次重试都会把完整上下文重新发送一遍。
我实验过在API限流条件下让Claude Code执行一个较大的重构任务。第一次请求返回限流错误后,它等待几秒自动重试;重试又把同样的历史消息和工具结果全量重发。如果限流持续,这一个任务就可能产生三到四次全量请求,配额消耗瞬间放大四倍。更糟的是,有些错误发生在工具调用阶段,比如工具返回了格式异常的JSON,模型解析失败后会不断尝试修正调用参数,形成“格式错误-重试-格式错误”的循环。
传统测试中的错误注入,验证的是“系统在异常下不会崩溃”,但agentic系统还需要验证“异常下的资源消耗是否收敛”。不加上这个维度,测试就漏掉了最大的成本黑洞。
4. 针对盲区设计更务实的Agent测试方案
4.1 把配额与成本作为一等测试指标
既然成本盲区是最核心的,那就该把成本量化成可断言的指标。我参照了传统性能测试的思路,在测试框架里增加了一个“agent成本探针”,用来记录每次测试任务消耗的token总量、请求次数、平均上下文大小和重试次数。
具体做法是,在调用Claude Code的SDK或CLI时,解析每次响应里的usage字段,把prompt_tokens、completion_tokens、total_tokens累计起来。测试结束后,用断言判断总消耗是否超过预设阈值。以下是一个简化版本的思路示意:
def test_refactor_cost_budget(): task = RefactorTask("fix flaky login tests") result = run_agent(task, enable_profiling=True) total_tokens = result.total_tokens request_count = result.request_count retry_count = result.retry_count # 预设预算:基于历史基线 x 1.5 的浮动上限 budget = get_baseline(task).total_tokens * 1.5 assert total_tokens < budget, \ f"token消耗超出预算: {total_tokens} > {budget}" assert retry_count <= 3, \ f"重试次数异常: {retry_count}"这个探针不需要侵入业务代码,只要在测试入口统一封装,整个Agent任务流都可以被监控。我还建议把成本指标写进CI流水线,作为每个PR的必跑检查项。试想一下,如果每次代码提交都由一个Agent自动完成,而这个Agent消耗的配额是浮动的,你不加成本门槛,团队月底的账单完全没有可预测性。
4.2 构建上下文膨胀与截断压力测试
针对盲区二,我设计了一套“上下文压力测试”用例。目的是验证Agent在长会话、高累积之下的行为是否仍然稳定。
做法是模拟不同长度的历史消息,从1K、5K、20K、50K到接近上下文上限,分别执行同一个任务,观察输出质量的下降曲线和token消耗的增速。通过这个测试,你可以得到三个关键数据:一是上下文每增长一倍,token消耗增长多少;二是超过多少token后,任务完成质量开始明显下降;三是触发上下文压缩/截断时,系统是否还能正确执行核心指令。
在我自己的项目里,这个测试帮了大忙。我发现当历史消息超过40K token后,模型经常“忘记”最开头的约束条件,于是我在产品层加了一个“关键约束重注入”机制,在每个关键节点前把最重要的任务约束重新发送一遍。这个优化上线后再跑压力测试,质量下降曲线明显平缓了很多。
4.3 用契约测试锁定工具调用格式
盲区三和盲区四都和工具调用有关。如果你的Agent要对接很多外部工具,给每个工具定义一份“调用契约”是性价比很高的做法。
契约测试的思路是:针对每个工具,定义输入参数的Schema、输出结果的Schema、以及常见错误响应的格式。然后模拟多组参数,验证Agent生成的调用是否严格符合契约。我把这步放在测试框架的错误注入模块里,专门构造“半合法”的输入来考验Agent。
比如某个工具要求返回JSON数组,但真实场景里工具可能返回了一个空对象或带了额外字段。契约测试会提前验证Agent面对这些畸形返回时是继续重试、忽略错误,还是向用户报告异常。通过这种方式,我抓到了好几个会导致重试风暴的契约漏洞。
此外,契约测试还能验证另一个东西——当工具调用失败时,Agent是否会把完整的错误信息塞进上下文。有些错误信息本身很大,一次失败可能凭空增加几千token。这个行为也应该被测试覆盖到,必要时要限制工具返回的大小,或者对错误信息做摘要。
4.4 错误注入与重试策略收敛性测试
针对盲区四,我建议所有Agent测试都增加“混沌模式”。做法是在测试环境里随机注入错误:API超时、限流、工具返回500、参数格式错误、进程被杀等等。
每次注入错误后,记录三个指标:任务最终是否完成、重试了几次、累计消耗了多少token。理想情况下,重试应该呈指数退避,重试次数有上限,且每次重试的开销不应显著高于首次请求。如果重试次数超过预设阈值,或者任务在错误中无限循环,测试应该直接失败。
这个做法让我发现了一个很有意思的现象:Claude Code在遇到特定类型的格式错误时,会尝试“修正后重试”,但它修正的往往是参数里的边角字段,核心错误始终没解决。这种“看似在努力、实际在原地打转”的行为,不用错误注入测试根本发现不了。
4.5 工具调用序列的收敛性断言
针对盲区三,我在测试框架里加了一个工具调用序列追踪器。每当Agent调用工具,就把工具名、参数哈希、返回结果哈希记下来,最后形成一个序列。测试断言不仅看最终结果,还看这个序列是否满足收敛条件。
我常用的几个断言模式如下:
- 同一工具连续调用次数不超过N次(默认3)。
- 相同参数的工具调用在一轮对话中不重复出现。
- 工具调用总次数不超过任务复杂度的某个倍数。
- 不存在“无进展的步骤”,即调用某个工具后,任务状态没有发生变化。
这些断言写起来不复杂,但效果非常好。我抓到一个很典型的回归:某个版本升级后,模型开始频繁调用list_files列出整个项目目录,连续列了七八次,每次结果都一样,却还在继续列。加上收敛性断言之后,这个问题在CI阶段就被卡住了,而不是等到真正消耗了用户配额才被发现。
5. 常见问题与排查技巧实录
5.1 常见问题速查表
我从实际项目中整理了一张速查表,遇到问题可以直接照着查:
| 症状 | 可能原因 | 排查方法 | 解决建议 |
|---|---|---|---|
| 配额消耗远高于预期 | 上下文无限膨胀、工具结果全文回传、错误重试放大 | 抓包或查CLI日志,统计每轮请求的token | 设置上下文截断策略,限制工具输出长度,加预算熔断 |
| 单次任务频繁重试 | API限流、工具返回格式不符、上下文过长导致生成质量下降 | 查看错误日志,统计重试间隔和退避策略 | 实现指数退避,严格执行工具调用契约,减少上下文冗余 |
| Agent重复执行同一工具 | 模型陷入循环或缺少任务状态记录 | 追踪工具调用序列,检查参数哈希 | 增加收敛性断言,为任务状态设置里程碑 |
| 上下文截断后行为异常 | 截断策略丢掉了关键约束,模型“失忆” | 构造长会话压力测试,观察质量下降点 | 关键约束重注入,或用摘要压缩替代粗暴截断 |
| 测试环境正常,生产环境成本超支 | mock环境与真实模型行为差异大 | 建立影子测试,用真实模型跑关键链路 | 把成本测试纳入CI,回归对比基线 |
5.2 踩坑后的几条避坑建议
写了这么多,我觉得最有复用价值的还是下面几条建议。第一条,Claude Code这类Agent应用测试一定要从“单轮对话正确性”升级为“多轮行为流的资源约束验证”。我见过很多团队辛辛苦苦写了大量功能测试,却没有人问一句:这个Agent跑一遍到底要花多少钱。
第二条,日志和埋点要前置。逆向分析虽然有用,但成本太高,不能每次线上问题都靠抓包来查。应该在一开始就把关键事件结构化成日志:每轮请求的token数、工具调用的参数摘要、错误码和重试次数。日志打得够细,排查时根本不需要逆向。
第三条,工具调用要设“闸门”。所有可能返回大体积内容的工具,都要在框架层做长度限制或者内容摘要。不要指望模型自己懂得克制,它只会把所有能用的信息都塞进上下文。工具能返回多少内容,应该由你的设计和测试来定,而不是让模型临场发挥。
第四条,预算熔断是最后一道防线,必须有。我后来给Agent执行器加了一个全局配额计数器,累计消耗超过设定阈值就停止任务、抛出警告。这个机制救过我很多次,尤其是夜里跑的自动化测试,一旦Agent发疯,至少不会烧穿整周配额。
6. 对Agent开发团队的额外提醒
6.1 不要把Agent测试等同于普通单元测试
我见过不少团队把Agent应用的测试写成了普通单元测试:把一次工具调用当作一个函数,断言函数返回值。这样做不是没用,但它只覆盖了“叶子节点”,覆盖不了Agent真正的核心逻辑——决策链。
Agent的复杂性在于多步推理和工具组合,测试的重心应该放在“链路”和“资源”上,而不是单步输出。最直观的测试方式,是定义一个任务清单,跑完整条链路后对比资源和结果。这比零散的单点测试有效得多。
6.2 建立基线很重要
做成本测试最怕没有参照物。同一个任务,不同模型版本的消耗天差地别;同一个版本,上下文长度不同也能差好几倍。所以在做Agent测试时,一定要为典型任务建立基线数据,包括平均token消耗、请求次数、重试次数和完成率。
有了基线,你才能回答“这个改动是变好了还是变坏了”这个问题。我一直把基线数据保存在CI的测试报告里,每次Agent相关代码改动都跑一次回归,自动对比基线和当前结果。超过浮动阈值就报错,方便定位是哪一版改动导致成本暴涨。
6.3 大胆利用本地日志做回放测试
逆向工程的副产品,是一套完整的真实执行日志。这些日志不仅能帮我排查问题,还能用来做回放测试:把线上或本地环境的Claude Code请求日志记录成标准格式,测试时用相同输入重新执行一遍,看新版本是否产生相同或更优的结果。
回放测试的好处是它不依赖真实模型,也不消耗配额,可以在CI里频繁跑。我把日志里的请求体、工具调用顺序、最终结果做成了json文件,测试框架直接读取这些数据做回归。如果有行为不一致,系统会标记出来,人工确认是新Bug还是预期改进。现在这套回放测试已经成了我们Agent仓库的守护者,每次提交都要过一遍。
我个人在实际操作中的体会是,Agent开发最容易被低估的就是成本与行为流。传统的单元测试和集成测试保护的是“代码逻辑完整性”,但像Claude Code这种自主调用工具的Agent,真正需要保护的是“行为可控性和预算可预测性”。如果不是这次配额烧得太快、逼得我去拆解请求和日志,我到现在可能还在傻乎乎地写那些只看结果不看成本的测试用例。最后再分享一个实用技巧:抓包分析和本地日志不要用完就删,整理成回放数据集,它能帮你省下的时间和配额,绝对比你当初逆向排查时花的功夫要多得多。