这种事我遇到过不止一次:晚上我和团队把某个区块的几个方案都过了一遍,第二天早上有人问,标题怎么变短了。聊天记录里有几十条消息,脑子里剩下的只是"背景好像是浅色的"之类的印象,而准确的决定早就淹没在一次次修改里了。
这种时候,requests.log 就派上用场了。这是一份简短的外部工作记录,用普通编辑器打开、从头往下读就行。我看重它不是因为行数多,而是因为它能把请求和最终真正保留下来的结果对上。

为什么需要外部记录
这些年我习惯把四样东西分开看:请求、决定、聊天记录,还有文件保存下来的版本。它们回答的是不同的问题,尽管在同一次会话里很容易混成一团说不清楚的经过。
| 来源 | 能从中知道什么 | 不能证明什么 |
|---|---|---|
requests.log | 提了什么请求,记录是什么时候出现的 | 改动是否已经完成并被接受 |
| 决定记录 | 选了哪个方案,是谁标记的 | 保存的文件现在长什么样 |
| 聊天记录 | 讨论的上下文和回复 | 什么内容真正进了项目的当前版本 |
| 版本控制系统 | 保存了哪些文件和哪些行 | 为什么偏偏选了这个方案 |
日志的作用像一个指针:带您回到准确的时间点和原话,但不会帮您恢复代码本身。要看保存下来的页面版本,还是得看文件和项目的版本历史。
这里有一条重要的分界线,我见过不少人在这上面得出错误结论。UserPromptSubmit 事件的 hook 会在 Claude Code 处理请求之前,从 prompt 字段拿到文本。这样一条记录只能证明文本确实发出去了,但这时候任务的结果还完全不知道。
这张图很清楚地展示了读日志时该注意的区别。上面那条链路展示的是工作的进展,下面那条展示的是最终留在日志里的内容。自动记录出现在结果之前;决定记录只在检查完之后才出现。二者互不替代。
一条有用的记录由什么组成
用三种记录方式来说明。这三种我在实际项目里都遇到过。
完成这样一行记录几乎没用:谁做了什么、什么时候做的、这个"完成"到底对应哪个任务,全都不清楚。
用户:把卡片标题改短一点……
Claude:好的,我先看一下这个组件……
用户:内边距也再小一点……
Claude:改好了……而这里又信息太多,不方便快速查找。完整的对话流放在聊天记录里正合适,但单独做成日志很快就会变成聊天记录的一份笨拙副本。我试过这么做,结果自己花时间在里面来回翻找。
2026-09-08T14:32:11+03:00 | prompt_submitted | actor=user | ref=card-header | text="把卡片标题缩短成一行"
2026-09-08T14:41:06+03:00 | decision_recorded | actor=user | ref=card-header | text="采用短标题,字号 24 px"我最喜欢第三种:它保留了一个简短的事实、带时区的时间、作者,还有把相关行连起来的 ref 标记。这里的字段名是为这个练习项目临时定的,是我们自己文件的格式,不是 Claude Code 规定的必备结构。在这个例子里,ref 标记是手动加到相关行上的。hook 不会猜请求属于哪个区块:基础的 UserPromptSubmit 事件本身不带这种语义标记。就算没有这个关联字段,您也照样可以在日志里按准确的短语来搜索。
记录当下能知道什么
prompt_submitted 这一行只能说明一件事:会话里进来了这样一条请求,hook 把它记了下来。通常这是您写的文本,但并非总是如此。UserPromptSubmit 在 /loop 的新一轮、后台 subagent 的汇报以及来自其他会话的消息上也会触发,所以我们格式里的 actor=user 只是教学示例的假设。它并不能说明 Claude Code 改了页面、检查通过了,或者大家都满意这个结果。
结果要等它真正明确之后再记。在我们的例子里,这是查看页面之后另外补的一条记录。如果您的格式里有 result 或 error 字段,填进去的应该是观察到的事实,而不是对任务未来结果的猜测。
决定该记在哪里
只有当有人明确把决定记下来之后,决定才算出现。上面的例子里,是用户看完页面之后加的这一行。actor 标明了是谁记的,ref=card-header 保留了和请求的关联。
这条记录可以是手动加的,这没问题。自动记录保存的是输入,而由人来确认结果的含义。一条既没有作者也没有关联的"完成",是不能信的。
读请求日志
来看一个五行的小日志。要还原决定的过程时,我通常就是从这种短片段入手的。
2026-09-08T14:32:11+03:00 | prompt_submitted | actor=user | ref=card-header | text="把卡片标题缩短成一行"
2026-09-08T14:36:20+03:00 | prompt_submitted | actor=user | ref=card-header | text="给我看两个方案:24 px 和 28 px"
2026-09-08T14:41:06+03:00 | decision_recorded | actor=user | ref=card-header | text="采用短标题,字号 24 px"
2026-09-08T15:03:44+03:00 | prompt_submitted | actor=user | ref=card-shadow | text="把卡片阴影改柔和一点"
2026-09-08T15:08:17+03:00 | prompt_submitted | actor=user | ref=card-header | text="标题颜色试试更深一点的"假设您需要找到关于标题格式最近一次被采纳的决定。先在编辑器里搜索 card-header。会出来四行。最后一行是关于颜色的新想法,不是决定。所以结论落在 14:41:06 这一行:采用了短标题,字号 24 px。
我读日志的顺序很简单:
- 先把问题说清楚:我要找的是请求、决定,还是观察到的错误。
- 找到共同的标记、组件名,或者准确的短语。
- 只留下需要的记录类型,按时间顺序读。
- 说出支撑结论的具体行。其他的一律老实标为未知。
这样的分析不启动 Claude Code 也能做。这是个有用的习惯:自己先拿文件核对一遍结论,然后再把同样的工作交给智能体。
日志里没有决定的情况
现在去掉 14:41:06 这一行。剩下的就是要求展示两个尺寸的请求,加上后来关于换颜色的请求。到底选了哪个尺寸?日志没有回答。
这种时候很容易想选 24 px,因为它是最先提出的方案。别这么做。请求只说明有对比方案的意图,不代表最终的选择结果。这里唯一诚实的结论是:日志里没有记录关于尺寸的决定。
分叉走向哪边不取决于请求出现的顺序,而取决于找到的记录类型。有明确的决定记录 - 结论可以确认。只剩下请求 - 就记为未知,不用猜测去填这个空白。这种克制最后能省下不少时间。
让 Claude Code 分析日志的时候,我会要求它说出结论具体依据哪几条记录。如果没有能证实的记录,这一点就应该进未知清单,而不是猜一个成功的结果。
日志里不该留下什么
我不打算把一份有用的日志变成什么都往里塞的仓库。记录该包含什么,要看以后需要回答的是哪种问题。想找决定,通常有时间、事件类型、作者、关联标记和一小段文字就够了。
- 不要拷贝密钥、token、
.env的内容,以及个人数据。 - 如果只需要状态或者操作 id,就不要把外部服务的响应整份搬进来。
- 只有真正需要的地方才保留完整 prompt。这篇文章里 prompt 是完整的,只是因为例子本身短小而且是虚构的。
- 定期检查文件大小,以及旧记录还有没有用。没有哪个统一的保留期限适用于所有项目。
把一行截到一百个字符能提升可读性,但不会自动清掉里面的密钥。一个短 token 完全可以塞进前一百个字符里。先想清楚哪些数据本来就允许记录。应该从这条规则开始,而不是从行的长度开始。
怎样和 Claude Code 一起分析日志
Claude Code 很擅长把时间顺序理出来,也能很快找到相关的行。但前提是它得真的去读那个文件:项目里有 requests.log 这件事本身,不代表它的内容已经进了上下文。
给智能体一个具体的任务:
请读取 requests.log。找出关于卡片标题尺寸(card-header)的
最后一条明确被采纳的决定。列出你依据的具体记录。
如果日志里只有一个想法,就照实说明。
另外单独列出还有哪些是未知的。一个好的回答会把事实和结论分开:引用时间、事件类型和决定的文字,然后把没有确认的疑问单独列出来。请把这些引用和原始行对照一下。当智能体把最后一个请求说成"已采纳的方案"时,这个错误尤其明显。
对这份练习日志来说,正确的回答应该是这样:尺寸的决定由 2026-09-08T14:41:06+03:00 这条记录确认;后来出现了一个关于更深颜色的请求,但颜色最终采纳了什么,仍然未知。我认为这样的回答才靠得住:它不会说得比文件里实际有的更多。
- hook 在请求被处理之前就保存了它,所以这条记录不能证明结果。
- 单独的记录靠时间或标记,把被采纳的决定和请求关联起来。
- 搜索从一个具体问题开始,最后落到对原始记录的引用上。
- 如果没有确认,日志就留一个空白。不需要靠猜测把它补上。
requests.log 就这样保持着一份小巧的工作记录:帮您找到讨论中关键的转折点,但它既不冒充项目的备份,也不假装是任务完成的证明。日志记得老实,您才会真的愿意回来查它。
GO TO FULL VERSION