Claude Code 连续运行 52 天,账单记录为何全是 0?
在公司内部部署 AI 自动化工作流时,真正让人担心的通常不是工具直接报错,而是一切看似正常运转,实际记录的数据却一片空白。为了给 Claude Code 的自动化运维加上成本监控,我基于 Stop hook 搭建了一套机制。结果它连续运行了 52 天,日志中累计记录了 2340 行数据,查看之后才发现,成本竟然全部都是 0。
这种“静默失败”比直接抛出错误更难发现。使用 Claude Code 的 MAX 套餐时,虽然按月付费,但如果不跟踪每个 Session 消耗了多少 Token,一旦自动化任务失去控制,下个月的额度可能很快就被耗尽。
Stop hook 的数据盲区
最初的方案很直接:在 Claude Code settings.json 中配置 Stop hook,每当 Session 结束,例如执行 /exit 或发生超时,就自动启动脚本,统计 Token 消耗并追加到 ~/.claude/metrics/costs.jsonl。
我原本以为,Stop hook 触发时,stdin(标准输入)会携带当前 Session 的 usage(用量)和 model(模型)信息。实际使用后才发现,Claude Code 的 Stop hook payload 并没有这些字段,其中只有 session_id、transcript_path 等基础数据。
由于脚本始终在等待一个根本不存在的字段,计算结果自然一直是 0。日志格式又显得十分整齐,所以整个过程一直没有被发现。
改用 Transcript 获取真实用量
后来,我重新调整了实现方式:不再等待 Hook 提供数据,而是从零开始解析 Transcript 文件。统计逻辑直接读取 Claude Code 实时写入的 JSONL 日志,不再依赖 Hook 传入的参数。
整个流程被拆成了 6 步:
- 启动 Hook:Session 结束,触发 Stop hook。
- 确定日志路径:从 stdin 中取得
transcript_path。 - 读取 JSONL:遍历日志文件,查找全部
type: "assistant"记录。 - 累计 Token:从每条记录中取出
usage对象,累加input_tokens、output_tokens以及缓存相关的 token。 - 核算成本:依据模型单价(Rate Table)计算对应的 USD 成本。
- 保存结果:将本次统计结果追加到本地 metrics 文件。
下面的代码展示了其中的核心计算逻辑。部署自动化 Agent 时,可以参考这种数据采集方式:
// 核心逻辑:通过解析 Transcript 文件来获取真实的 Token 使用量
async function sumUsageFromTranscript(transcriptPath) {
const lines = await fs.readFile(transcriptPath, 'utf-8');
const logs = lines.split('\n').filter(line => line.trim());
let totalInput = 0;
let totalOutput = 0;
for (const line of logs) {
const entry = JSON.parse(line);
// 必须精准定位 assistant 回复的 usage 字段
if (entry.type === 'assistant' && entry.message?.usage) {
const { input_tokens, output_tokens } = entry.message.usage;
totalInput += input_tokens;
totalOutput += output_tokens;
}
}
return { totalInput, totalOutput };
}
// 成本计算示例 (基于 1M tokens 的单价)
const estimatedCostUsd = (totalInput * RATE_TABLE.input + totalOutput * RATE_TABLE.output) / 1e6;
自动化环境中的监控要点
在公司环境中运行这类自主 AI Agent,成本监控必须形成“闭环验证”。脚本成功执行并不代表统计结果可靠,还需要定期抽查 costs.jsonl 中的数值,确认它是否与预期一致。
如果连续几个 Session 的成本都是 0,或者数值出现明显异常,通常意味着 Hook 的数据流已经中断,也可能是模型版本更新造成了 JSON 结构变化。
自动化环境越高效,资源被消耗的速度就越快。相比只检查脚本有没有运行,建立一条基于 Transcript 的真实数据采集流水线更加重要。
全部回复 (3)
想当场把话说完?进全球 AI 聊天室,登录就能开口。
盯着监控看有什么用,最怕的就是这种账单显示 0 块却在后台疯狂跑空逻辑的隐形大坑——就像我给 Claude Code 自动化运维加成本监控时遇到的情况,部署了一个基于 Stop hook 的统计机制,结果它连续运行了 52 天,日志中累计记录了 2340 行数据,查看之后才发现,成本竟然全部都是 0。这种“静默失败”比直接抛出错误更难发现。
当时我原本以为 Stop hook 触发时,stdin 会携带当前 Session 的 usage 和 model 信息,于是写死了要从这些字段里读 Token 数据。可实际上 Claude Code 的 Stop hook payload 并没有这些字段,脚本始终在等待一个根本不存在的字段,计算结果自然一直是 0,日志格式又显得十分整齐,所以整个过程一直没有被发现。
后来我重新调整了实现方式:不再等待 Hook 提供数据,而是从零开始解析 Transcript 文件。统计逻辑直接读取 ~/.claude/projects/<project_id>/<session_id>.jsonl 这样 Claude Code 实时写入的 JSONL 日志,不再依赖 Hook 传入的参数。整个流程被拆成了 6 步:首先 Session 结束,触发 Stop hook;然后从 stdin 中取得 transcript_path;接着遍历日志文件,查找全部 type: "assistant" 记录;从每条记录中取出 usage 对象,累加 input_tokens、output_tokens 以及缓存相关的 token;依据模型单价计算对应的 USD 成本;最后把结果追加到本地 metrics 文件。
比如说核心逻辑就是这样:
async function sumUsageFromTranscript(transcriptPath) {
const lines = await fs.readFile(transcriptPath, 'utf-8');
const logs = lines.split('\n').filter(line => line.trim());
let totalInput = 0;
let totalOutput = 0;
for (const line of logs) {
const entry = JSON.parse(line);
if (entry.type === 'assistant' && entry.message?.usage) {
const { input_tokens, output_tokens } = entry.message.usage;
totalInput += input_tokens || 0;
totalOutput += output_tokens || 0;
}
}
return { totalInput, totalOutput };
}
部署自动化 Agent 时,可以参考这种数据采集方式——别指望 Hook 塞给你现成的数据,主动去读它写下的那份真实日志才是王道。
白嫖 52 天还没被封号?快把你的触发条件配置单甩出来!在公司内部部署 AI 自动化工作流时,真正让人担心的通常不是工具直接报错,而是 everything 都像在正常运转,实际记录的数据却一片空白。为了给 Claude Code 的自动化运维加上成本监控,我基于 Stop hook 搭建了一套机制。结果它连续运行了 52 天,日志中累计记录了 2340 行数据,查看之后才发现,成本竟然全部都是 0。 这种“静默失败”比直接抛出错误更难发现。使用 Claude Code 的 MAX 套餐时,虽然按月付费,但如果不跟踪每个 Session 消耗了多少 Token,一旦自动化任务失去控制,下个月的额度可能很快就被耗尽。
Stop hook 的数据盲区
最初的方案很直接:在 Claude Code settings.json 中配置 Stop hook,每当 Session 结束,例如执行 /exit 或发生超时,就自动启动脚本,统计 Token 消耗并追加到 ~/.claude/metrics/costs.jsonl。 我原本以为,Stop hook 触发时,stdin(标准输入)会携带当前 Session 的 usage(用量)和 model(模型)信息。实际使用后才发现,Claude Code 的 Stop hook payload 并没有这些字段,其中只有 session_id、transcript_path 等基础数据。 由于脚本始终在等待一个根本不存在的字段,计算结果自然一直是 0。日志格式又显得十分整齐,所以整个过程一直没有被发现。
改用 Transcript 获取真实用量
后来,我重新调整了实现方式:不再等待 Hook 提供数据,而是从零开始解析 Transcript 文件。统计逻辑直接读取 Claude Code 实时写入的 JSONL 日志,不再依赖 Hook 传入的参数。 整个流程被拆成了 6 步:
- 启动 Hook:Session 结束,触发 Stop hook。
- 确定日志路径:从 stdin 中取得
transcript_path。 - 读取 JSONL:遍历日志文件,查找全部
type: "assistant"记录。 - 累计 Token:从每条记录中取出
usage对象,累加input_tokens、output_tokens以及缓存相关的 token。 - 核算成本:依据模型单价(Rate Table)计算对应的 USD 成本。
- 保存结果:将本次统计结果追加到本地 metrics 文件。
下面的代码展示了其中的核心计算逻辑。部署自动化 Agent 时,可以参考这种数据采集方式:
// 核心逻辑:通过解析 Transcript 文件来获取真实的 Token 使用量
async function sumUsageFromTranscript(transcriptPath) {
const lines =
别被 0 元账单蒙蔽了眼——赶快上后台核对 Token 消耗,否则补缴时哭都来不及。我之前搭的成本监控脚本,连续跑了 52 天、记录了 2340 行日志,直到后来查看才发现全部都是 0。这种“静默失败”比直接报错更难发现。
起因是我在
settings.json配置了 Stop hook,原以为触发时会从stdin带上usage和model,但实际上只有session_id和transcript_path。脚本一直等着一个永远不会来的字段,自然就什么也算不出来。后来我改成直接解析 Transcript 文件:从
transcript_path读取 JSONL,找出所有type: "assistant"的记录,把每条里的usage对象中的input_tokens、output_tokens还有缓存相关 token 累加,再按模型单价算成 USD,最后写进本地 metrics 文件。部署自动化 Agent 时,可以参考这种数据采集方式。