第 23 章:场景四:调试与排错
场景定位:第五篇实战篇的第 4 个场景。前三个场景分别覆盖了理解陌生项目、从零搭建新功能、大规模重构。本章聚焦于开发工作中最让人头疼也最考验功力的环节——调试与排错。
调试是开发者与代码之间的侦探游戏。你面对的是一个沉默的嫌疑人(Bug),手上只有零星的线索(日志、报错堆栈、用户反馈),需要在庞大的代码库中找出真相。传统调试依赖个人经验和运气——有人看一眼报错就知道问题在哪,有人加了一整天的 console.log 仍然一头雾水。
Claude Code 改变了这个游戏的规则。 它不会替代你的判断力,但它能在几分钟内完成你可能需要几小时才能做完的"地毯式搜索"——读取所有相关文件、追踪完整调用链、对比历史变更、提出系统性假设。本章将通过一个完整的生产环境 Bug 排查案例,展示如何将 Claude Code 作为你的调试搭档。
学习完本章后,你将能够:
- 使用 Claude Code 从模糊报错中快速形成排查假设
- 掌握假设驱动的系统化调试流程
- 让 Claude 追踪跨文件的调用链和依赖关系
- 验证修复并防止同类问题再次出现
23.1 场景描述
来自用户的模糊报错
周五下午 4:58,你正准备关电脑过周末。Slack 上弹出一条消息:
用户反馈:登录后有时会立刻被踢回登录页,要重新登录才行。不是每次都出现,大概每登录 10 次出现 2-3 次。Chrome 最新版,网络正常。
你打开日志平台,搜索该用户近一小时的记录。唯一相关的错误是:
2026-05-30 16:42:31 ERROR [auth.middleware] Token validation failed
- userId: usr_8a3f2
- ip: 203.0.113.45
- userAgent: Mozilla/5.0 ... Chrome/132.0
- traceId: 7b9c4-2e1f-4a8d就这一行。没有堆栈信息,没有更详细的错误原因。Token validation failed 可能意味着任何事情——Token 过期、签名不匹配、解码失败、Redis 连接超时……
你叹了口气,重新打开终端。
为什么这类 Bug 最磨人
这个 Bug 具备所有"难啃的骨头"的特征:
| 特征 | 为什么麻烦 |
|---|---|
| 不可稳定复现 | 每 10 次出现 2-3 次,暗示可能是竞态条件或超时问题 |
| 错误信息模糊 | "Token validation failed" 覆盖了十几种可能的根因 |
| 涉及多个系统 | Auth 服务 → JWT 签发 → Redis Session → 中间件校验 |
| 没有堆栈 | 日志只记录了错误描述,没有调用栈帮助定位 |
传统做法下,你可能会:在每个怀疑点加 console.log → 部署到测试环境 → 想办法复现 → 看日志 → 再加更多 log → 重复……这个过程可能持续几小时甚至一整天。
而有了 Claude Code,你的调试流程将完全不同。
23.2 Step 1:让 Claude Code 分析错误日志
从一行日志开始
你没有急着去翻代码,而是打开 Claude Code 侧边栏,把仅有的线索粘贴进去:
我在排查一个生产环境的 Bug。用户反馈登录后偶尔会被立即登出,
大概每 10 次出现 2-3 次。唯一的错误日志是:
"ERROR [auth.middleware] Token validation failed
- userId: usr_8a3f2
- ip: 203.0.113.45
- traceId: 7b9c4-2e1f-4a8d"
帮我分析这个错误可能的原因。先不要改代码,只做分析。Claude 的系统性分析
Claude 读完你的描述后,没有立刻下结论,而是提出了一个结构化的假设列表:
根据你提供的信息,我分析了以下几种可能的原因,按可能性从高到低排列:
假设 1(高可能性):Token 过期时间过短
如果 JWT 的 expiresIn 设置得太短(比如 60 秒),
在登录流程中某些步骤耗时较长时,Token 可能在
校验前就过期了——这解释了"偶尔"出现而非每次。
假设 2(中可能性):服务器时钟不同步
如果签发 Token 的服务器和校验 Token 的服务器
之间存在时钟偏差(NTP 未同步),即使 Token 本身
未过期,校验端也可能认为它已过期。
假设 3(中可能性):Redis 连接不稳定
如果 Session 信息存储在 Redis 中,偶尔的连接
超时或连接池耗尽可能导致 Token 校验时查不到
Session,造成验证失败。
假设 4(低可能性):Token 签名密钥不一致
如果多实例部署时,签发和校验使用了不同的密钥,
则会出现间歇性的签名验证失败。
假设 5(低可能性):并发请求导致 Token 刷新竞争
如果前端在 Token 即将过期时发起了多个并发请求,
可能触发多次刷新,导致某次请求使用了被撤销的旧 Token。
要进一步缩小范围,我需要查看相关代码。
要我逐一检查这些假设吗?这个分析的质量值得注意:
- 按可能性排序:不是无差别罗列,而是基于经验给出优先级
- 每个假设都解释了为什么能匹配症状:"偶尔出现"这个关键特征被反复关联
- 主动提出下一步:不等你指令,Claude 已经准备好了执行计划
追踪 Token 的完整生命周期
你回复:
帮我查看 JWT 相关的代码,找出 token 生成和验证的完整逻辑。
从登录接口开始追踪,一直到中间件校验。Claude Code 开始工作。它的执行步骤清晰可循:
1. Grep 搜索 "jwt" 关键词 → 找到 8 个相关文件
2. 读取 src/api/auth/login.ts — 登录接口入口
3. 读取 src/utils/jwt.ts — Token 生成工具函数
4. 读取 src/middleware/auth.ts — Token 校验中间件
5. 读取 src/config/auth.config.ts — 认证相关配置
6. 读取 src/services/session.ts — Session/Redis 管理读完后,Claude 给出了完整的调用链梳理:
Token 生命周期追踪:
【签发阶段】src/api/auth/login.ts → src/utils/jwt.ts
1. 用户提交用户名+密码
2. login.ts 调用 verifyCredentials() 验证凭据
3. 验证通过后,调用 jwt.signToken({ userId, role })
4. signToken 内部配置:
- payload: { userId, role }
- secret: process.env.JWT_SECRET
- options: { expiresIn: '60s' } ← 这里!
5. 生成的 Token 返回给前端,同时存入 Redis Session
【校验阶段】src/middleware/auth.ts
1. 每个请求进入时,中间件从 Authorization header 提取 Token
2. 调用 jwt.verifyToken(token) 验证签名和过期时间
3. 如果 Token 有效,从 Redis 读取 Session 补充用户信息
4. 如果 Redis 读取失败 → 返回 "Token validation failed"
5. 如果 Token 过期 → jwt.verify 抛出 TokenExpiredError
我注意到的关键点:
- src/utils/jwt.ts 第 23 行:expiresIn: '60s'
- src/middleware/auth.ts 第 47 行:Redis 读取失败时只记录了
"Token validation failed",没有区分"Token 本身无效"
和"Redis 读取失败"第一条关键线索浮出水面:expiresIn: '60s' —— Token 只有 60 秒的有效期。
23.3 Step 2:假设驱动的调试流程
逐一检验每个假设
有了 Token 生命周期的全景图,你开始让 Claude 逐个检验前面的假设。
检验假设 1:Token 过期时间
检查一下 jwt.ts 中 expiresIn 的设置,
60 秒对于生产环境来说正常吗?Claude 的回答直接而明确:
生产环境中 JWT 的典型过期时间参考:
| 场景 | 推荐 expiresIn |
|------|---------------|
| Access Token | 15 分钟 ~ 1 小时 |
| Refresh Token | 7 天 ~ 30 天 |
| 一次性验证 Token(邮箱验证等) | 5 ~ 10 分钟 |
| API Key | 长期,通常不过期 |
当前设置 60 秒 = 1 分钟。
这远低于行业标准的最小值(15 分钟)。
如果用户在登录后花 30 秒浏览首页,再点击一个
需要鉴权的链接,Token 已经只剩 30 秒。
如果网络稍有延迟或中间件处理耗时稍长——
Token 过期,用户被登出。
这能解释"偶尔出现"的症状:
- 快速操作的用户不会触发(Token 在过期前被使用)
- 稍慢的用户就会遇到(Token 已过期)
- 取决于用户操作速度,所以不可稳定复现检验假设 2:时钟问题
检查一下代码中时间比较是用服务器时间还是客户端时间Claude 检查了 jwt.ts 和 auth.ts:
Token 的签发和校验都使用 jsonwebtoken 库的标准行为:
- 签发时:jwt.sign() 使用服务器当前时间设置 iat(issued at)
- 校验时:jwt.verify() 使用服务器当前时间与 exp 比较
- 没有使用客户端时间
所以时钟问题只有在多服务器实例间不同步时才可能发生。
但从日志来看,traceId 7b9c4... 的所有请求都落在同一台
服务器上(通过 Pod 名称推断)。时钟问题的可能性较低。检验假设 3:Redis 连接
检查 session 存储的 Redis 连接配置Claude 读取了 src/services/session.ts 和 Redis 配置:
src/services/session.ts 第 15 行:
const redisClient = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
connectTimeout: 5000, // 连接超时 5 秒
maxRetriesPerRequest: 3, // 单次请求最多重试 3 次
retryStrategy: (times) => Math.min(times * 200, 2000),
});
src/middleware/auth.ts 第 42-50 行:
try {
const session = await redisClient.get(`session:${userId}`);
if (!session) {
logger.error('Token validation failed', { userId, ip, traceId });
return res.status(401).json({ error: 'Token validation failed' });
}
} catch (redisError) {
// 注意:这里捕获了 Redis 错误,但日志记录的仍是
// 'Token validation failed'——和 session 不存在时完全一样
logger.error('Token validation failed', { userId, ip, traceId });
return res.status(401).json({ error: 'Token validation failed' });
}Claude 指出了两个问题:
Redis 相关发现:
1. 连接配置看起来合理(5 秒超时,3 次重试),
正常网络条件下不应频繁失败。
2. 但中间件的错误处理有严重问题:
- Session 不存在 → 日志:"Token validation failed"
- Redis 连接失败 → 日志:"Token validation failed"
- 两者完全一样,无法从日志区分根因!
建议修复:在 catch 块中记录不同的错误信息,
比如 "Redis connection failed during token validation"。
这样下次出现时可以立刻判断是否是 Redis 的问题。锁定根因
三个假设检验完毕,证据指向非常明确:
假设 1(Token 过期):
✅ 发现 expiresIn: '60s',远低于生产环境标准
✅ 能完美解释"偶尔出现"的症状模式
✅ 修复最简单、影响最可控
假设 2(时钟问题):
❌ 无多实例部署(请求落单台服务器)
❌ 无 NTP 相关告警
假设 3(Redis 问题):
⚠️ Redis 配置本身合理,但错误日志区分度不够
⚠️ 即使 Redis 有问题,也不是当前 Bug 的主因
(因为 Token 过期会先于 Redis 读取被 jwt.verify 拦截)结论明确:expiresIn: '60s' 是根因。
追查历史:这个 60 是怎么来的
在动手修复之前,你还想确认一件事——这个 60 秒是设计如此还是手误。
这个 60 是故意的还是写错了?
查看 jwt.ts 的 git log,看这个值是谁改的、什么时候改的。Claude Code 执行了 git log:
$ git log --oneline --all -10 -- src/utils/jwt.ts
c4e8a2f feat: add JWT authentication (2026-05-15)
+ expiresIn: '60s'
$ git show c4e8a2f -- src/utils/jwt.ts
commit c4e8a2f
Author: dev@example.com
Date: 2026-05-15 14:22:00
+ const token = jwt.sign(payload, secret, { expiresIn: '60s' });
对比同文件中的注释:
+ // TODO: 上线前改为 3600s(1 小时)
所以 60 秒是开发阶段的临时设置,但TODO 没有被执行——
代码带着开发配置上线了。真相大白。开发时为了方便测试(Token 快点过期,方便验证刷新逻辑),设置了 60 秒。注释里写了 TODO: 上线前改为 3600s,但这个 TODO 在代码审查和上线流程中被遗漏了。
经典的人为失误:开发配置残留到生产环境。这类问题靠人工 Code Review 很难抓干净——因为审查者会自然地假设"这行代码一定是有意为之的"。Claude Code 的价值在于它能不带假设地审视每一行代码,将"60s"和"行业标准 15 分钟 ~ 1 小时"之间的巨大差异标注出来。
23.4 Step 3:逐步缩小问题范围
修复根因
确定了根因,修复就很简单了:
帮我修复这个问题,把过期时间改为 3600 秒。
另外把那个 TODO 注释也删掉。Claude Code 用 Edit 工具精确修改了 src/utils/jwt.ts:
// 修改前:
const token = jwt.sign(payload, secret, { expiresIn: '60s' }); // TODO: 上线前改为 3600s
// 修改后:
const token = jwt.sign(payload, secret, { expiresIn: '3600s' });搜索同类问题
你没有就此止步。一个地方出了问题,项目中其他地方很可能也会有同样的问题。
同时检查一下项目中是否还有其他地方用了同样的错误值。
搜索所有包含 "60s" 或 "expiresIn" 的代码。Claude 执行了全项目搜索:
$ grep -rn "expiresIn" src/
src/utils/jwt.ts:23: const token = jwt.sign(payload, secret, { expiresIn: '3600s' });
src/utils/emailToken.ts:12: const token = jwt.sign({ email }, secret, { expiresIn: '60s' });
src/services/passwordReset.ts:35: const resetToken = jwt.sign({ userId }, secret, { expiresIn: '60s' });
src/config/session.ts:8: ttl: 60, // 单位:秒Claude 报告:
找到了 3 处相关的配置:
1. src/utils/jwt.ts — ✅ 已修复为 3600s
2. src/utils/emailToken.ts:12 — expiresIn: '60s'
用途:邮箱验证 Token
评估:邮箱验证链接通常在邮件中,用户需要时间查看邮件、
点击链接。60 秒太短,建议改为 600s(10 分钟)
3. src/services/passwordReset.ts:35 — expiresIn: '60s'
用途:密码重置 Token
评估:同上,用户需要收邮件、点击链接、填写新密码。
60 秒基本不可能完成,建议改为 600s
4. src/config/session.ts:8 — ttl: 60
用途:Session 在 Redis 中的过期时间
评估:60 秒的 Session TTL 极不正常。这意味着用户登录后
如果 60 秒内没有操作,Session 就被清除了。
建议至少 86400s(24 小时)原来不止一处。全项目有 4 处使用了开发阶段的 60 秒配置,都是同一个模式——开发时为了方便测试设得很短,注释写了 TODO,然后被遗忘了。
你让 Claude 一次性修复所有问题:
把这三处也都修复:
- emailToken.ts → 600s
- passwordReset.ts → 600s
- session.ts → 86400s(24 小时)Claude Code 用 Edit 工具逐一修改了每个文件。完成后,四处的配置都更新为合理的生产环境值。
改进错误日志
还记得中间件里Redis 错误和 Session 不存在被记录为同一个日志的问题吗?虽然它不是当前 Bug 的根因,但如果不修复,下一次排查会再次被误导。
之前你提到的中间件日志问题——Redis 错误和 Session 不存在的日志
完全一样。帮我把它们区分开来。Claude Code 修改了 src/middleware/auth.ts 的错误处理:
// 修改后:
try {
const session = await redisClient.get(`session:${userId}`);
if (!session) {
logger.warn('Session not found in Redis', { userId, traceId });
return res.status(401).json({ error: 'Session expired, please login again' });
}
} catch (redisError) {
logger.error('Redis connection error during token validation', {
userId,
traceId,
error: redisError.message,
});
return res.status(503).json({ error: 'Service temporarily unavailable' });
}关键改进:
| 改进点 | 修改前 | 修改后 |
|---|---|---|
| Session 不存在 | 记录 ERROR 级别 | 降级为 WARN(正常业务流程) |
| Redis 连接失败 | 与 Session 不存在混淆 | 独立错误信息 + 记录原始错误 |
| HTTP 状态码 | 统一返回 401 | 区分 401(认证失败)和 503(服务不可用) |
| 用户提示 | 模糊的 "Token validation failed" | Session 过期有明确提示 |
防御性调试:好的错误日志不只是记录"出错了",而是要回答三个问题——谁(userId/traceId)、在哪(具体位置)、为什么(根因而非表象)。当你下一次排查问题时,清晰的日志能帮你省下几个小时。
23.5 Step 4:验证修复
编写测试用例
修复代码只是第一步。你需要确认修复是有效的,并且不会引入新的问题。
帮我写一个测试用例,验证 Token 过期时间是否正确。
测试应该:创建 Token → 检查 exp 字段 → 断言过期时间Claude Code 读取了现有的测试文件结构和测试框架配置,然后创建了测试用例:
// src/utils/__tests__/jwt.test.ts
import { describe, it, expect } from 'vitest';
import jwt from 'jsonwebtoken';
import { signToken } from '../jwt';
describe('JWT Token 生成', () => {
it('Token 过期时间应为 3600 秒(1 小时)', () => {
const userId = 'test_user_001';
const token = signToken({ userId, role: 'user' });
// 解码 Token(不验证签名)以读取 payload
const decoded = jwt.decode(token) as jwt.JwtPayload;
expect(decoded).toBeDefined();
expect(decoded.iat).toBeDefined();
expect(decoded.exp).toBeDefined();
// 验证过期时间 = 签发时间 + 3600 秒
const expiresIn = decoded.exp! - decoded.iat!;
expect(expiresIn).toBe(3600);
// 验证 payload 中的 userId 正确传递
expect(decoded.userId).toBe(userId);
expect(decoded.role).toBe('user');
});
it('emailToken 过期时间应为 600 秒(10 分钟)', () => {
const { signEmailToken } = require('../emailToken');
const token = signEmailToken('user@example.com');
const decoded = jwt.decode(token) as jwt.JwtPayload;
const expiresIn = decoded.exp! - decoded.iat!;
expect(expiresIn).toBe(600);
});
it('passwordReset Token 过期时间应为 600 秒(10 分钟)', () => {
const { signPasswordResetToken } = require('../../services/passwordReset');
const token = signPasswordResetToken('test_user');
const decoded = jwt.decode(token) as jwt.JwtPayload;
const expiresIn = decoded.exp! - decoded.iat!;
expect(expiresIn).toBe(600);
});
});测试写好后,你运行了测试:
pnpm test -- jwt三个测试全部通过。
手动验证登录流程
在部署到生产环境之前,你还想手动验证一下完整的登录流程。
帮我写一个 cURL 命令,手动验证完整的登录流程:
1. 登录获取 Token
2. 用 Token 访问受保护的接口
3. 确认返回 200 而不是 401Claude 生成了一套可供手动执行的验证命令:
# 1. 登录获取 Token
TOKEN=$(curl -s -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"test_user","password":"test_pass_123"}' \
| jq -r '.token')
echo "Token: ${TOKEN:0:20}..."
# 2. 用 Token 访问受保护接口
curl -s -X GET http://localhost:3000/api/user/profile \
-H "Authorization: Bearer $TOKEN" \
| jq .
# 3. 验证 Token 中的 exp 字段(获取过期时间)
echo "$TOKEN" | cut -d'.' -f2 | base64 -d 2>/dev/null | jq '.exp - .iat'
# 预期输出:3600你逐条执行了这些命令,返回结果符合预期——登录成功、接口返回 200、Token 过期时间显示 3600 秒。
部署前确认清单
在将修复推向生产之前,Claude 帮你整理了一份确认清单:
部署前检查清单:
□ Token 过期时间修复
✅ src/utils/jwt.ts: expiresIn 60s → 3600s
✅ src/utils/emailToken.ts: expiresIn 60s → 600s
✅ src/services/passwordReset.ts: expiresIn 60s → 600s
□ Session 配置修复
✅ src/config/session.ts: ttl 60 → 86400
□ 错误日志改进
✅ src/middleware/auth.ts: 区分 Session 过期和 Redis 故障
□ 测试覆盖
✅ jwt.test.ts: 三个过期时间验证用例全通过
□ 手动验证
✅ cURL 登录流程测试通过
□ 代码审查
✅ 所有变更仅涉及配置值修改和日志增强,无逻辑变更
□ 回滚方案
git revert <commit-hash> 即可一键回滚
□ 监控关注点(上线后)
- 观察 401 错误率是否下降
- 关注 "Session not found" 警告频率(可能有用户需重新登录)
- 关注 Redis 连接错误日志(区分后可以单独监控)23.6 复盘与技巧总结
调试的系统化方法
这次排查之所以高效,不是因为你运气好,而是因为你遵循了一套系统化的调试流程:
┌─────────────────────────────────────────────────────┐
│ 系统化调试流程 │
├─────────────────────────────────────────────────────┤
│ │
│ 1. 日志分析 │
│ ├─ 搜集所有可用的错误信息 │
│ ├─ 让 Claude 从日志中提取关键字段 │
│ └─ 识别症状模式(频率、时间分布、受影响用户) │
│ │
│ 2. 假设驱动 │
│ ├─ 基于经验提出多个假设(按可能性排序) │
│ ├─ 为每个假设设计验证方法 │
│ └─ 让 Claude 并行检查多个假设 │
│ │
│ 3. 逐一排除 │
│ ├─ 从可能性最高的假设开始验证 │
│ ├─ 每个假设 → 检查代码 → 确认或排除 │
│ └─ 缩小排查范围,聚焦最可能的根因 │
│ │
│ 4. 定位根因 │
│ ├─ 确认根因并追溯引入历史(git log) │
│ ├─ 搜索同类问题(全项目扫描) │
│ └─ 识别关联问题(如日志区分度不够) │
│ │
│ 5. 修复 │
│ ├─ 修复根因 + 所有同类问题 │
│ └─ 修复过程中发现的次生问题 │
│ │
│ 6. 验证 │
│ ├─ 单元测试(自动化验证) │
│ ├─ 手动测试(端到端验证) │
│ └─ 部署前确认清单 │
│ │
└─────────────────────────────────────────────────────┘Claude Code 在调试中擅长什么
| 擅长 | 说明 | 本章对应环节 |
|---|---|---|
| 大规模代码搜索 | 几秒内扫描全项目,找出所有相关代码 | 搜索 expiresIn 找到 4 处配置 |
| 追踪调用链 | 从入口到出口完整追踪函数调用 | 登录接口 → JWT 签发 → 中间件校验 |
| 对比差异 | 找出代码中不一致的地方 | 60s vs 行业标准 3600s |
| 搜索相似问题 | 在当前项目中搜索同类错误模式 | 发现 3 处相同的遗留 TODO |
| 生成测试 | 针对 Bug 编写精准的测试用例 | 过期时间断言测试 |
| 历史追溯 | 通过 git log/blame 找到变更历史 | 确认 60s 是开发配置残留 |
| 系统性分析 | 基于有限信息提出结构化假设 | 5 个假设按可能性排序 |
Claude Code 在调试中不擅长什么
| 不擅长 | 为什么 | 替代方案 |
|---|---|---|
| 运行时状态调试 | Claude 无法连接调试器、设置断点、查看运行时的变量值 | 传统断点调试、console.log、远程调试工具 |
| 环境特定问题 | 无法访问你的生产/测试环境的实际状态 | 提供日志文件、错误截图、环境信息描述 |
| UI/视觉 Bug | 无法"看到"页面渲染效果 | 提供截图、录制复现视频 |
| 需要反复交互的调试 | 不能在 while 循环中"改一点 → 跑一下 → 看结果 → 再改" | 描述清楚复现步骤,Claude 一次性分析 |
| 性能分析 | 无法运行 profiler、分析 CPU/内存快照 | 提供 profiling 报告文本,Claude 辅助分析 |
| 第三方服务问题 | 无法检查外部 API 状态、数据库实际数据 | 提供 API 响应、数据库查询结果 |
理解边界很重要:Claude Code 最适合的是"静态分析型调试"——读代码、追踪逻辑、分析错误模式。对于需要运行时代码执行的调试场景,Claude 是辅助而非主力。
关键技巧
技巧 1:给 Claude 完整的错误信息
# ❌ 信息不完整
"登录有问题,帮我看看代码哪里错了"
# ✅ 信息完整
"用户反馈登录后偶尔被登出。错误日志:
'ERROR Token validation failed, userId: xxx'
相关文件在 src/api/auth/ 和 src/middleware/ 下。
先分析可能原因,不要直接改代码。"技巧 2:让它先读代码再判断
Claude 有一个常见的倾向:基于你的描述直接给出修复建议,而不是先去读代码确认。你应该主动要求它先读后判:
"帮我看看这个 Bug。先读取所有相关的文件,
理解逻辑后再给我分析。不要跳过读代码这一步。"技巧 3:系统性排查,而非随机猜测
# ❌ 随机猜测
"可能是 Token 过期了吧,帮我改一下过期时间"
# ✅ 系统性排查
"列出所有可能导致 'Token validation failed' 的原因。
然后逐个检查代码,排除不可能的原因。"技巧 4:并行检验多个假设
当 Claude 提出多个假设后,同时检查它们,而不是串行:
"你提了三个假设:Token 过期、Redis 连接、时钟不同步。
帮我同时检查这三个方向的相关代码,看看哪个最能解释
'偶尔出现'的症状模式。"技巧 5:修 Bug 时顺手改进日志
每次修 Bug 都是一次改进可观测性的机会。如果你发现日志信息不足以定位问题——趁现在改掉它。下一次排查时你会感谢现在的自己。
技巧 6:修复一个,搜索同类
在修复一个 Bug 之后,永远让 Claude 做一次全项目搜索:
"这个 Bug 的模式是 [描述模式]。搜索项目中是否还有
其他地方有同样的问题。"本章的案例就是最好的证明——一个 expiresIn: '60s' 后面藏着三个相同的定时炸弹。
23.7 本章小结
本章通过一个完整的生产环境调试案例,展示了 Claude Code 作为调试搭档的工作方式。
核心流程:
错误日志(一行模糊的报错)
→ 假设驱动分析(5 个可能原因,按可能性排序)
→ 代码追踪(Token 签发 → 校验 完整调用链)
→ 逐一验证(检验每个假设,排除不可能的)
→ 定位根因(expiresIn: '60s' — 开发配置残留)
→ 追溯历史(git log 确认 TODO 未执行)
→ 同类搜索(全项目发现 4 处相同问题)
→ 一并修复(根因 + 关联问题 + 日志改进)
→ 测试验证(单元测试 + 手动 cURL)
→ 部署确认(检查清单)关键收获:
| 收获 | 要点 |
|---|---|
| 系统化方法 | 日志分析 → 假设驱动 → 逐一排除 → 定位根因 → 修复 → 验证 |
| Claude 的强项 | 代码搜索、调用链追踪、假设生成、历史追溯、测试生成 |
| Claude 的弱项 | 运行时调试、环境特定问题、UI 问题 |
| 核心习惯 | 先读后判、并行验证、修一搜全、改进日志 |
| 思维转变 | 从"自己读代码 → 自己猜 → 自己改"变成"Claude 读 → Claude 猜 → 你判断 → Claude 改" |
下一章:第 24 章:场景五:代码审查 —— 学习如何使用 Claude Code 进行系统化的代码审查,包括 PR Review 的最佳实践和审查视角。