大家好,我是子昕,一个干了10年的后端开发,现在在AI编程这条路上边冲边摸索,每天都被新技术追着跑。
最近几天我发现一个有趣的现象:作为 Claude Pro 订阅用户,我明显感觉到 Claude Code 有点降智
了,不如之前那么聪明。
这让我突然想起一个经典问题——我们能否偷看一下 AI 的“小抄”?
作为一个有着强烈好奇心的程序员,我决定对 Claude Code 来一场“开膛破肚”式的逆向分析。毕竟,既然它被誉为“AI编程工具之王”,那我就要看看它的王座到底是怎么坐稳的。
经过一番折腾,我发现了 GitHub 上一个对Claude Code进行逆向的项目:claude-code-reverse
这个项目可以让我们实时拦截和分析 Claude Code 与服务器的所有通信,相当于给 AI 装了个窃听器
。
为什么要逆向 Claude Code?
在开始动手之前,先说说为什么要这么做:
- 技术好奇心:Claude Code 凭什么能做到比其他 AI 编程工具更强?成本透明度:作为 Pro 用户,我想知道每次对话到底消耗了哪个模型,用了多少 tokens学习借鉴:了解顶级 AI Agent 的设计思路,对我们自己开发 AI 应用有巨大价值质量监控:当感觉 AI 表现异常时,可以通过日志分析找到原因
准备工作:工具箱清单
在开始这场“技术侦探”之旅前,你需要准备:
- Claude Code:废话,没有目标怎么逆向Node.js 环境:用于安装 js-beautify一颗不怕搞坏东西的心:记得备份,万一搞砸了别哭
第一步:定位“目标”
首先要找到 Claude Code 的真身。在命令行执行:
which claude
通常会得到类似这样的结果:
/opt/homebrew/bin/claude
但这只是个“替身”!在 Mac 上,这通常是一个软链接。我们需要找到真正的 cli.js
文件:
ls -l /opt/homebrew/bin/claude
你会看到它指向了真正的安装位置:
/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js
这就是我们要“动手脚”的地方!
第二步:美化代码,让它“可读”
Claude Code 的代码是压缩过的,就像一团乱麻。我们需要先让它变得人类可读:
# 进入 Claude Code 安装目录cd /opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/# 备份原文件(这一步很重要!)mv cli.js cli.bak# 安装代码美化工具npm install -g js-beautify# 美化代码js-beautify cli.bak > cli.js
现在 cli.js
就变成了格式良好、可读性强的代码。
第三步:植入“间谍代码”
这是整个过程中最关键的一步。我们要在 cli.js
中植入监控代码,让它把所有与 LLM 的对话都记录下来。
3.1 添加基础监控模块
在文件开头 #!/usr/bin/env node
这行之后,添加我们的“间谍模块”:
间谍代码如下,直接复制粘贴即可:
// ============= 间谍模块开始 =============import fs from "fs";import path from "path";import { fileURLToPath } from 'url';const __filename = fileURLToPath(import.meta.url);const __dirname = path.dirname(__filename);const LOG_PATH = path.resolve(__dirname, 'messages.log');// 每次启动时创建新的日志会话fs.writeFileSync( LOG_PATH, `---Session ${new Date()}---\n`);function isAsyncIterable(x) { return x && typeof x[Symbol.asyncIterator] === 'function'; }const ts = () => new Date().toISOString();function uid() { return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;}// 这个函数负责拦截流式响应,记录工具调用的详细信息function tapIteratorInPlaceWithTools(inner, onFinal) { if (!inner) return inner; const TAPPED = Symbol.for('anthropic.tap.iterator'); if (inner[TAPPED]) return inner; Object.defineProperty(inner, TAPPED, { value: true, configurable: true }); const byteLen = s => typeof Buffer !== 'undefined' ? Buffer.byteLength(s, 'utf8') : new TextEncoder().encode(s).length; const makeWrapper = getOrigIter => function() { const it = getOrigIter(); let text = ''; const open = new Map(); const done = []; const PREVIEW_CAP = Infinity; const start = (id, name) => { if (id == null || open.has(id)) return; open.set(id, { id, name: name||'unknown', startedAt: Date.now(), inputBytes: 0, preview: '' }); }; const delta = (id, chunk) => { if (id == null) return; if (!open.has(id)) start(id); const rec = open.get(id); if (!rec) return; const s = typeof chunk==='string' ? chunk : JSON.stringify(chunk||''); rec.inputBytes += byteLen(s); if (rec.preview.length < PREVIEW_CAP) { rec.preview += s.slice(0, PREVIEW_CAP - rec.preview.length); } }; const stop = id => { const rec = open.get(id); if (!rec) return; open.delete(id); const finishedAt = Date.now(); done.push({ ...rec, finishedAt, durationMs: finishedAt - rec.startedAt }); }; const finalizeDangling = err => { for (const rec of open.values()) { done.push({ ...rec, finishedAt: Date.now(), durationMs: Date.now() - rec.startedAt, errored: err ? (err.stack||String(err)) : undefined }); } open.clear(); }; return (async function*() { try { for await (const ev of it) { // 记录文本增量 if (ev?.type==='content_block_delta' && ev.delta?.type==='text_delta' && typeof ev.delta.text==='string') { text += ev.delta.text; } // 记录工具调用开始 if (ev?.type==='content_block_start' && ev.content_block?.type==='tool_use') { start(ev.index, ev.content_block.name); } // 记录工具调用过程中的数据增量 if (ev?.type==='content_block_delta') { const d = ev.delta; if (typeof d.input_json_delta==='string') delta(ev.index, d.input_json_delta); if (typeof d.input_text_delta==='string') delta(ev.index, d.input_text_delta); if (typeof d.tool_use_delta==='string') delta(ev.index, d.tool_use_delta); if (d.type==='input_json_delta') delta(ev.index, d.partial_json); if (d.type==='input_text_delta') delta(ev.index, d.partial_text); } // 记录工具调用结束 if (ev?.type==='content_block_stop' && ev.content_block?.type==='tool_use') { stop(ev.index); } if (ev?.type==='tool_use') { if (ev.start) start(ev.index, ev.name); if (typeof ev.delta==='string') delta(ev.index, ev.delta); if (ev.stop) stop(ev.index); } yield ev; } finalizeDangling(); onFinal({ text, tools: done }); } catch (e) { finalizeDangling(e); onFinal({ text, tools: done }); throw e; } })(); }; // 处理异步迭代器 const origSym = inner[Symbol.asyncIterator]; if (typeof origSym==='function') { Object.defineProperty(inner, Symbol.asyncIterator, { value: makeWrapper(origSym.bind(inner)), configurable: true, writable: true }); return inner; } if (typeof inner.iterator==='function') { Object.defineProperty(inner, 'iterator', { value: makeWrapper(inner.iterator.bind(inner)), configurable: true, writable: true }); if (!inner[Symbol.asyncIterator]) { Object.defineProperty(inner, Symbol.asyncIterator, { value: () => inner.iterator(), configurable: true }); } } return inner;}// ============= 间谍模块结束 =============
3.2 拦截 API 调用
接下来,我们需要找到 this.completions =
这行代码(通过搜索功能),在它后面插入 API 拦截代码:
拦截代码如下,同样直接复制粘贴:
// ============= API 拦截开始 =============// 对 beta.messages.create 进行补丁,记录所有 API 调用{ const origCreate = this.beta.messages.create.bind(this.beta.messages); this.beta.messages.create = (...args) => { const params = args[0] || {}; const callUid = uid(); // 记录请求 fs.appendFileSync(LOG_PATH, `\n${ts()} uid=${callUid} input: ${JSON.stringify(params)}\n`); const ret = origCreate(...args); // 处理非流式响应 if (!params.stream) { ret.then( data => fs.appendFileSync(LOG_PATH, `${ts()} uid=${callUid} output: ${JSON.stringify(data)}\n`), err => fs.appendFileSync(LOG_PATH, `${ts()} uid=${callUid} error: ${err.stack||String(err)}\n`) ); return ret; } // 处理流式响应 return ret._thenUnwrap((data, _props) => { if (isAsyncIterable(data)) { return tapIteratorInPlaceWithTools(data, final => fs.appendFileSync(LOG_PATH, `${ts()} uid=${callUid} stream.final: ${JSON.stringify(final)}\n`) ); } return data; }); };}// ============= API 拦截结束 =============
第四步:准备分析工具
下载逆向分析项目:
git clone https://github.com/Yuyz0112/claude-code-reverse.gitcd claude-code-reverse
第五步:开始监控
现在一切准备就绪!当你使用 Claude Code 时,所有的 API 交互都会被记录到 messages.log
文件中。
让我们来个简单的测试:
你会发现在 Claude Code 安装目录下生成了一个 messages.log
文件:
第六步:解析和可视化
使用项目提供的解析工具处理日志:
cd claude-code-reversenode parser.js /path/to/messages.log
然后浏览器打开 visualize.html
文件,点击“Choose conversation log”按钮,选择你本地生成的log文件进行可视化分析:
分析结果:Claude Code 的“内心独白”
现在让我们来看看 Claude Code 在处理一个简单问题时的完整思考过程。我让它解释项目结构,然后通过日志分析看到了它的“内心戏”。
第一幕:开场白 - 配额检测
发现:Claude Code 每次启动都会先发送一个神秘的 “quota” 消息,使用的是 Haiku 3.5 模型。这就像是在说“喂,服务器,我还有额度吗?”这是一个轻量级的心跳检测,确保后续的对话能正常进行。
分析:这个设计很聪明,用最便宜的模型做检查,避免浪费昂贵的 Sonnet 4 额度。
第二幕:身份确认 - 话题检测
发现:接下来 Claude Code 会使用 [prompt_1]
来判断这是否是一个新的会话,并尝试从用户输入中提取 2-3 个关键词作为会话标题。
prompt_1 的作用:
判断当前是否为新会话,如果是,从用户输入中提取2-3个词作为此次会话的标题。
依然使用 Haiku 3.5 模型,说明这也是一个轻量级任务。
第三幕:正式登场 - 核心工作流程
重头戏来了! 这次使用的是 Sonnet 4 模型,系统会注入多个关键提示词:
- 系统角色中:
[prompt_4]
+ [prompt_5]
用户消息中:[prompt_2]
+ [prompt_3]
这里的 [prompt_5]
是整个 Claude Code 的大脑
,定义了它的完整行为模式。
在下面我会完整贴出这几个提示词的中文版,让你一探究竟。
第四幕:开始行动 - 工具调用
Claude Code 的第一反应:使用 LS
工具查看当前目录结构。
这就像人类程序员接到任务后第一件事就是 ls
看看当前环境一样,AI 也学会了这个习惯!
系统响应:立即执行 LS
工具,将结果返回给模型。
第五幕:深入调查 - 文件读取
AI 的逻辑推理:既然看到了文件列表,下一步就是读取关键文件:
README.md
- 了解项目概述package.json
- 了解项目依赖和脚本这个行为模式和有经验的开发者如出一辙!
第六、七幕:持续探索
持续的信息收集:Claude Code 继续使用 READ
和 LS
工具,深入了解项目结构。每一次工具调用都有明确的目的性。
大结局:总结输出
完美收官:基于收集到的所有信息,Claude Code 给出了详细而准确的项目结构分析。
关键发现:多模型协作策略
通过这个完整的分析,我发现了 Claude Code 的智能之处:
1. 分层模型使用策略
- Haiku 3.5:配额检查、话题检测等轻量级任务Sonnet 4:核心推理、复杂分析、工具调用
2. 工具调用的智能序列
LS
→ 了解环境READ README.md
→ 获取项目概述READ package.json
→ 了解技术栈继续深入探索...这个序列完全模拟了人类开发者的思维模式!
2. 系统提示词的精巧设计
Claude Code 会在每次对话中注入多个系统提示词,我把主要的翻译如下:
系统提醒 - 开始部分
对应[ prompt_2]
<系统提醒> 在回答用户问题时,你可以使用以下上下文:# 重要指令提醒做用户要求的事情;不多不少。永远不要创建文件,除非它们对实现目标绝对必要。总是优先编辑现有文件而不是创建新文件。永远不要主动创建文档文件(*.md)或README文件。只有在用户明确要求时才创建文档文件。重要:此上下文可能与你的任务相关,也可能不相关。除非与你的任务高度相关,否则你不应该回应此上下文。</系统提醒>
系统提醒 - 待办事项
对应[prompt_3]
<系统提醒> 这是一个提醒,提醒你待办事项列表当前为空。不要明确地向用户提到这一点,因为他们已经知道了。如果你正在处理的任务可以从待办事项列表中受益,请使用 TodoWrite 工具创建一个待办事项列表。如果没有,请尽管忽略。同样,不要向用户提及此消息。</系统提醒>
核心身份定义
对应[prompt_4]
你是 Claude Code,Anthropic 官方的 Claude CLI 工具。
核心工作流程指令
对应[prompt_5],这是最重要的系统提示词,定义了 Claude Code 的完整行为模式:
你是一个交互式 CLI 工具,帮助用户完成软件工程任务。使用下面的指令和可用工具来协助用户。重要:仅协助防御性安全任务。拒绝创建、修改或改进可能被恶意使用的代码。允许安全分析、检测规则、漏洞解释、防御工具和安全文档。重要:你绝不能为用户生成或猜测 URL,除非你确信这些 URL 是为了帮助用户编程。你可以使用用户在消息中提供的或本地文件中的 URL。如果用户寻求帮助或想要给出反馈,请告知他们以下信息:/help:获取使用 Claude Code 的帮助要给出反馈,用户应该在 https://github.com/anthropics/claude-code/issues 报告问题当用户直接询问 Claude Code(如"Claude Code 能做..."、"Claude Code 有...")或以第二人称询问(如"你能..."、"你可以做...")时,首先使用 WebFetch 工具从 Claude Code 文档中收集信息来回答问题,文档地址:https://docs.anthropic.com/en/docs/claude-code语气和风格:你应该简洁、直接、切中要点。你必须用少于4行的内容简洁回答(不包括工具使用或代码生成),除非用户要求详细信息。重要:你应该尽可能减少输出令牌,同时保持有用性、质量和准确性。只处理具体的查询或任务,避免无关信息,除非对完成请求绝对关键。如果你能在1-3句话或一个短段落中回答,请这样做。重要:你不应该用不必要的开场白或结尾回答(比如解释你的代码或总结你的行动),除非用户要求你这样做。除非用户要求,否则不要添加额外的代码解释摘要。处理文件后,直接停止,而不是提供你做了什么的解释。直接回答用户的问题,不要详细说明、解释或细节。一个词的答案是最好的。避免介绍、结论和解释。你必须避免在回应前后添加文本,如"答案是..."、"这里是文件的内容..."或"基于提供的信息,答案是..."或"这是我接下来要做的..."主动性:你可以主动,但只有在用户要求你做某事时。你应该努力在以下方面取得平衡:- 在被要求时做正确的事情,包括采取行动和后续行动- 不要用未经请求的行动让用户感到惊讶遵循约定:当对文件进行更改时,首先了解文件的代码约定。模仿代码风格,使用现有的库和实用程序,遵循现有模式。永远不要假设给定的库是可用的,即使它是众所周知的。每当你编写使用库或框架的代码时,首先检查这个代码库是否已经使用了给定的库。任务管理:你可以访问 TodoWrite 工具来帮助你管理和计划任务。非常频繁地使用这些工具,以确保你正在跟踪你的任务并给用户你的进度可见性。重要:一旦你完成了一个任务,就立即将待办事项标记为已完成。不要在标记为已完成之前批量处理多个任务。执行任务:用户主要会要求你执行软件工程任务。这包括解决错误、添加新功能、重构代码、解释代码等。对于这些任务,建议采用以下步骤:1. 如果需要,使用 TodoWrite 工具来计划任务2. 使用可用的搜索工具来理解代码库和用户的查询3. 使用所有可用的工具实现解决方案4. 如果可能,用测试验证解决方案非常重要:当你完成一个任务时,你必须运行 lint 和 typecheck 命令(如 npm run lint, npm run typecheck, ruff 等)如果它们被提供给你,以确保你的代码是正确的。重要:永远不要提交更改,除非用户明确要求你这样做。只有在明确要求时才提交是非常重要的,否则用户会觉得你太主动了。
3. 智能的上下文管理
当对话变长时,Claude Code 会自动压缩历史上下文,保留关键信息的同时节省 token 消耗。这个功能通过专门的压缩提示词实现。
4. 工具调用的精细化设计
Claude Code 定义了丰富的工具集,包括:
- 文件系统操作(读取、写入、搜索)代码执行(Bash、Python 等)任务管理(TodoWrite)IDE 集成工具子代理系统(Task)
实际应用:解决“降智”问题
通过日志分析,我发现我感觉到的“降智”现象可能有几个原因:
- 模型选择策略变化:可能是为了控制成本,某些任务改用了较轻量的模型上下文压缩过于激进:重要信息在压缩过程中丢失工具调用链过长:复杂任务的多步骤推理被分散到多个工具调用中
总结
通过这次“技术侦探”之旅,我不仅了解了 Claude Code 强大秘密的冰山一角,还学到了一些 AI Agent 设计的宝贵经验:
- 多模型协作比单一模型更高效精心设计的系统提示词是关键工具系统的丰富性决定了能力上限上下文管理策略影响对话质量
这只是开始
需要说明的是,这次分析只是通过一个简单的“描述项目结构”需求,初步了解了如何进行逆向分析。Claude Code 的真正实力远不止于此!
后面我计划通过更复杂的场景来深入挖掘:
复杂编程任务
:看它如何处理多文件重构、架构设计等高难度任务性能优化场景
:分析它如何进行代码审查和性能调优调试和问题解决
:观察它的错误诊断和修复策略项目搭建全流程
:从零开始创建一个完整项目的思维过程Sub-Agent 系统
:深入了解它的多智能体协作机制每一个场景都会揭示 Claude Code 更深层的设计哲学和技术细节。如果你对某个特定场景特别感兴趣,欢迎留言告诉我!
如果你也想探索 AI 的内心世界,不妨试试这个方法。记住,好奇心是程序员最宝贵的品质!