资讯详情

Claude Code源码深度拆解:从CLI入口到Agent循环的工程实践

📅 2026/10/25 22:11:22 | 华诺云谱 👁 阅读
Claude Code源码深度拆解:从CLI入口到Agent循环的工程实践
简介Claude Code 的完整源码以 zip 压缩包形式提供适合希望深入理解 AI 编程助手实现原理的开发者、研究者以及正在进阶 TypeScript 工程化的学员参考。资源共包含 1903 个文件主体为 1332 个 ts 文件与 552 个 tsx 文件另有少量 js 脚本和 md 文档压缩后体积约 9.43MB便于快速下载和本地阅读。源码在结构上严格遵循模块化与面向对象设计核心算法、数据处理逻辑与工具函数拆分清晰并通过大量注释、API 文档和测试用例保证可读性与可维护性。与此同时项目依托 Git 管理代码变更开发者指南和用户手册等材料也为后续二次开发提供了明确指引。目前已有 289 人学习下载适合作为学习大型开源项目架构、代码组织方式和工程实践的学习样本。1. Claude Code源码这不是又一个AI壳子而是一套端到端的coding agent工程先说个反直觉的结论Claude Code在终端里跑起来只是命令行工具但真正拉开差距的是它那套渗透到源码层面的代号为“harness”的调度机制。很多人以为装个npm包就要排队等Claude的API实际用下来你会发现它能听懂你在编辑器里选中了哪一段代码能在你翻车的时候自动做unit test循环修复这才是源码级工程该有的样子。这份源码包适合谁两类人最赚一是被各种GUI套壳工具折磨过的前端和全栈开发者想从命令行拿到上下文感知的AI辅助二是想研究“agent循环”——也就是让模型自己感知、计划、执行、验证的工程师。你拿到的不只是代码而是一整套能跑起来的agent框架。我的建议是别当普通工具安装直接去读它的entry point和bash executor相关源码比翻十篇博客都涨功力。以下内容来自我对这套源码实拆后的记录从架构到部署、从参数到避坑全部是实操踩过的路径。项目本体是基于TypeScript的harness实现下载源码包后按下面的步骤走十分钟内能跑起来之后慢慢拆。2. 拆源码先看骨架从CLI入口到agent循环的调用链2.1 顶层目录一眼认出这条链路里谁在干什么Claude Code的源码工程解压后第一件事不是跑npm install而是先把目录结构摸了。它的核心分四块cli/面向用户的所有命令入口也就是你在终端里输入的claude命令对应的逻辑。harness/agent循环的核心负责把模型输出转成行动。包括plan、exec、observation三个环节的衔接。integrations/和外部工具通信的适配层比如编辑器插件VSCode、Git、终端命令执行。tools/模型允许调用的工具集合比如读文件、写文件、执行bash命令、搜索代码。还有一个容易被忽略的bash-server/目录这是专门在隔离环境里执行shell命令用的项目通过它规避注入风险。这个设计直接关系到后面避坑章节里“命令执行权限”的问题先留个印象。2.2 入口文件拆解命令行参数是怎么变成agent动作的找到cli/entrypoint.js部分版本里叫cli/entrypoint.ts这是整个程序的起点。它不是简单读取参数然后调函数而是用了一个类事件循环的机制。核心逻辑伪码如下// 简化后的入口逻辑 async function main() { const options parseCommandLineArgs(); const conversation new Conversation(options); // 会话管理保留上下文 const runtime new HarnessRuntime({ model: options.model, maxTurns: options.maxTurns }); // 首次用户输入进来后进入agent循环 const userTask options.prompt || await readStdin(); const result await runtime.run(userTask, { cwd: options.cwd, allowedTools: options.allowedTools, onStepComplete: (step) printStep(step, options.verbose), }); if (options.outputFormat json) { console.log(JSON.stringify(result, null, 2)); } }这里有几个参数要说明allowedTools决定模型能调用哪些工具默认是全量允许但生产环境一般要白名单收敛。maxTurns是agent循环的最大轮数默认50我一般会调到80因为复杂任务经常要“尝试-失败-再试”好几轮。onStepComplete是回调钩子你可以在这里做日志、状态上报、甚至做自定义的门禁校验。这个入口的设计思路是把命令行参数解析和真正的agent逻辑解耦。参数只是一堆配置真正打架的活全在HarnessRuntime里也就是下一节要拆的循环。2.3 HarnessRuntime调度细节工具调用的“解释-执行-验证”三步真正的核心在harness/runtime.ts这里实现了agent循环。简单来说它做三件事把模型返回的文本解释成结构化的工具调用执行调用把结果反馈给模型。循环直到模型认为任务完成或触发最大轮数为止。我摘一段核心的循环代码这是理解全源码的关键async run(userTask: string, ctx: RuntimeContext): PromiseRunResult { let messages: Message[] [{ role: user, content: userTask }]; for (let turn 0; turn ctx.maxTurns; turn) { const modelResponse await this.model.complete(messages, { tools: ctx.allowedTools, // 关键参数强制模型思考后再行动能大幅减少无效工具调用 thinking: { budget: 2000 }, }); messages.push(modelResponse.message); if (modelResponse.stopReason end_turn) { return { status: done, messages }; } // 解析模型输出中的工具调用意图 const toolUse parseToolUse(modelResponse.text); if (!toolUse) { // 模型回答了但没有调用工具判定为无效回复给它一次纠错机会 messages.push({ role: assistant, content: 你的回复没有触发工具调用请重新规划并选择一个工具。, }); continue; } const toolResult await this.executeTool(toolUse, ctx); messages.push({ role: user, content: toolResultContent(toolResult) }); } return { status: max_turns_exceeded, messages }; }这里值得在源码里重点标注的是thinking.budget这个参数。Claude Code能“想清楚再动手”靠的是让模型在内部生成综合性的中间思维但如果预算给得太大每一轮耗时会明显拉长给得太小复杂任务容易开局就翻车。经验值是2000到4000之间。executeTool内部会根据工具名走不同分支bash类工具会送进bash-server独立进程执行文件工具直接在项目进程内操作。这个隔离设计是安全底线源码改了这里就等于改了安全模型不建议动。3. 把源码跑起来从零到能独立改代码的最小可运行路径3.1 环境准备与依赖安装源码是TypeScript工程跑起来需要Node.js环境。我这边测试用的是Node 20npm 10。版本太老的Node会在编译依赖时直接报错建议线上跑之前先node -v确认。# 1. 安装依赖最好用pnpmnpm有时候会卡在postinstall脚本上 pnpm install # 2. 构建所有包cli、harness、integrations一次全编译 pnpm build # 3. 把命令行工具软链到全局方便直接跑 npm link注意第一行如果报权限错误检查npm源是不是被修改过企业内部源经常缺包切回官方源再试一次。pnpm build在这份源码里不会只编译一个包而是按依赖顺序构建全部子包所以输出比较长看到多个built提示就是成功。3.2 认证配置拿到会话密钥并设置模型参数Claude Code依赖Anthropic的API但这里的认证方式不是直接把API Key写在配置里而是走一个登录会话机制。跑claude命令会提示你去浏览器里完成授权完成后密钥会存在本地的配置目录里。# 初始化配置按提示完成登录 claude # 如果你想指定模型和开关用下面的参数组合 claude --model claude-3-7-sonnet-latest --max-turns 80--model参数要注意不同版本源码支持的模型标识符不完全一样如果跑--help看不到你想要的模型去model-registry相关源码里查一份常量列表别凭记忆猜。--max-turns上面提过控制单次对话最大循环轮数默认50对大多数任务够用但你让它一口气修20个编译错误的话50轮确实会不够。还有一个非交互模式适合在CI里跑# 非交互模式直接给任务输出结果后退出 claude -p 读一下 src/main.ts找出所有未处理的 Promise rejection 并修复 --output-format json这个模式配合--output-format json非常实用你可以在CI脚本里拿到它修改过的文件列表和状态。JSON结构里messages就是完整对话记录fileEdits是实际改动文件的路径集合自己写校验脚本时直接用这两个字段。3.3 自定义工具扩展加一个“获取PR变更列表”的工具源码的价值在扩展性。官方没提供“Github PR信息拉取”能力但通过写一个工具函数并注册进去模型就能学会用它。工具注册的代码在tools/registry.ts里有明确的位置直接加进数组即可。// 在 tools/registry.ts 里追加这个工具 const githubTools [ { name: get_pr_changes, description: 获取指定PR的变更文件列表和每处变更的diff, input_schema: { type: object, properties: { prNumber: { type: number, description: PR编号比如 342 }, repo: { type: string, description: 仓库名如 owner/repo }, }, required: [prNumber, repo], }, async execute({ prNumber, repo }: { prNumber: number; repo: string }) { const response await fetch(https://api.github.com/repos/${repo}/pulls/${prNumber}/files, { headers: { Accept: application/vnd.github.v3.diff }, }); return await response.text(); }, }, ];注册以后模型在收到“分析PR #342 的改动影响”这类请求时就会自主选择调用这个工具。input_schema写不好会导致模型频繁传错参数——两个最典型的错误是required数组里漏字段、或者字段描述太模糊。描述要具体到“仓库名格式如 owner/repo不要带 git 前缀”这种程度模型才会正确生成参数。这种改造方式不用动harness主循环只要工具上报的返回内容足够简单直接模型理解成本低成功率就上去了。4. 四个必踩的坑配置、依赖和误判哪个都能卡你半小时4.1 模型显示“Not Available in Your Country”现象第一次跑claude登录完成后控制台直接打印一行note: claude code might not be available in your country. check supported countries然后退出。原因源码里内置了一组受支持的国家或地区代码判断依据是账户资料里的地区设置。这一步在网络正常状态下也会被触发确实是账户归属地判断不是终端环境能绕过的。解决确认账户归属地。如果你长期使用不支持地区的账户看下onboarding相关源码里面对supportedCountries有明确的常量定义。检查你当前用来登录的账户是否匹配。和网络环境本身没有关系不要试图通过修改网络出口来解决换个账户或修改账户地区设置是唯一通道。4.2pnpm install卡在 postinstall 不动现象依赖安装时长时间停在anthropic-ai/claude-code: postinstall或esbuild相关脚本。原因这份源码里 esbuild 的二进制包需要在安装时下载对应平台的二进制文件如果当前 npm 源不包含该包postinstall 脚本就会一直重试。国内开发者常遇到的另一个情况是环境变量里配置了不兼容的代理导致下载请求被拦。解决先清掉代理环境变量再装unset HTTP_PROXY HTTPS_PROXY ALL_PROXY pnpm install --no-frozen-lockfile注意不要开任何系统级代理工具让 esbuild 直接走默认通道下载成功率最高。如果换源务必选一个 esbuild 二进制完整的镜像源否则即使安装成功启动时也会报esbuild: command not found。4.3 修改代码后pnpm build不重新编译现象你改了cli/下的某个源文件重新跑claude命令发现改动不生效。原因npm link软链到的目录不是你当前源码目录而是官方包的全局安装目录。解决用pnpm build之后直接跑源码目录的入口文件不要依赖软链node cli/entrypoint.js --model claude-3-7-sonnet-latest如果你想继续用claude命令必须确认软链指向which claude ls -l $(which claude) # 看指向是否是你当前工作目录软链指向错了就重新npm link指向对了但还是旧代码那就是 build 输出目录缓存问题执行rm -rf dist pnpm build强制全量构建。4.4 工具调用陷入死循环同一段代码反复改现象让Claude Code修复一个lint错误结果它改一个文件跑一次lint报错再改再跑。有时候同一个位置改了七八次都没法收敛。原因这类问题大概率是模型在“修复”和“验证”之间缺少足够的信息传递——它看不到lint的完整输出或者工具返回里只有摘要没有具体行列号。循环结构本身没问题问题在信息密度。解决检查工具返回内容是否被截断。在harness/runtime.ts里找toolResultContent这个函数看它对长输出的截断策略。如果你被截断到只有前500字符模型当然只能盲猜。把截断阈值调高并强制保留错误出现的上下文尾部function toolResultContent(result: ToolResult): string { const fullOutput result.text; // 默认只保留头部修改为保留头部和尾部各1000字符 if (fullOutput.length 2000) { return ${fullOutput.slice(0, 1000)}\n...中部省略...\n${fullOutput.slice(-1000)}; } return fullOutput; }这样改完后模型能同时看到附件的错误开头和收尾信息对“哪里错了”的判断会准确很多。5. 让agent更懂你的项目代码库上下文工程的三个有效手段5.1 用CLAUDE.md喂项目经验效果比任何prompt都持久Claude Code源码里对CLAUDE.md有天然的支持每个目录下如果有这个文件它的内容会被自动注入到全局上下文中。这比你在对话开头粘贴一大段说明靠谱得多因为每次新对话它都会自动加载。我在项目根目录放的内容一般分三块项目技术栈清单什么语言、什么框架、包管理器是什么常见操作约定测试命令、lint命令、构建命令历史踩坑记录这个项目最容易出错的地方在哪以及为什么# CLAUDE.md - 项目指引 ## 技术栈 - 后端Node.js 20 TypeScript - 前端React 18 Vite - 包管理器pnpm ## 常用命令 - 本地开发pnpm dev - 跑测试pnpm test -- --runInBand - 构建pnpm build pnpm start ## 容易踩坑的点 1. src/db/migrations 下的文件不要手动改用 pnpm migration:generate 生成 2. 修改 API 路由后需要同时更新 openapi.yaml否则 CI 会挂 3. 不要直接在 main 分支上跑 --force先切 feature 分支模型在遇到相关任务时会直接从这些条目里找依据。比如你让它改数据库相关代码它就自己去读migrations目录并意识到“这个项目对迁移文件有特殊约定”。这种上下文注入方式比你在对话里反复强调要稳定很多而且可以随项目演进持续更新。5.2 自定义slash command把高频操作压缩成一条命令源码支持在.claude/commands/目录下创建自定义命令格式是markdown文件文件名就是命令名。比如我写了一个fix命令用来一键跑“跑测试-修编译错误-检查lint”的triple loop--- description: 修复失败的测试或编译错误 argument-hint: 可选的失败范围描述如只修复utils模块 --- 我会按顺序执行以下操作 1. 读取 package.json 中的 test 脚本定义 2. 运行 pnpm test 并记录失败用例 3. 针对失败用例逐个定位源码问题 4. 修复后重新运行测试验证 用户输入的范围限制{{argument_hint}} 如果没有明确范围默认处理全部失败用例。{{argument_hint}}是占位符输入claude /fix 只处理auth模块时这里会被替换成“只处理auth模块”。这比每次手动敲“请帮我跑一下测试如果挂了就修修完再跑”高效十倍。创建完重启claude会话即可在交互界面里看到新命令。这个方法适合把团队流程固化成模板让模型遵守统一的代码路径。5.3 利用会话恢复把长任务拆成多段跑源码里有个容易被忽略但很实用的能力会话持久化。在交互式进程退出后对话记录会保留在磁盘上。下次运行带--resume或--continue参数可以接上一次的上下文继续干。claude --continue -- 继续处理刚才的编译错误还剩3个这意味着你可以把一个大任务拆给不同时段的它去干。比如上午让它扫描代码库并列出需要重构的模块下午回来直接--continue让它基于上午的分析结果开始改代码上下文没断。这在处理大型代码库时特别有用不用一次性把上下文撑爆。6. 调出“全程广播”模式用verbose日志和dry-run把黑盒变成白盒--verbose参数是Claude Code里最被低估的调试工具。默认输出只是简明结果但带上这个参数以后harness会把每一步模型思考、工具选择、具体执行参数都打印出来。我第一次打开的时候才真正搞懂“为什么它要改这个文件”——原来是在前一步的错误日志里猜了个根源而它选工具的依据全在这一层日志里。我推荐的标准调试流程是这样# 1. 先用 dry-run 看计划如果需要 claude -p 重构 src/utils/date.ts 中的日期格式化函数 --dry-run # 2. 正式跑但开 verbose 记录完整决策链 claude -p 重构 src/utils/date.ts 中的日期格式化函数 --verbose 21 | tee run.log--dry-run如果源码版本支持会在不真正改动文件的情况下展示它打算执行哪些操作。这对评估“改动范围是否符合预期”非常有用——尤其是在批量重构场景下先看行动计划确认无误再放行改真实文件。tee run.log是Linux/macOS下的标准操作Windows PowerShell 用户换成| Tee-Object run.log。verbose日志里最值得盯的三个模式第一Tool Use段看模型调了什么工具、传了什么参数。如果它频繁调用Read而不是Grep说明代码库太大模型在靠读文件碰运气找内容性能会明显变慢。这时候最好在CLAUDE.md里明示代码结构。第二Observation段看工具返回结果里模型截取了什么。如果模型总是忽略报错里的文件路径那属于上下文管理问题调小返回截断阈值不要调大逼它用Grep定位。第三Thinking段中的“推理预算用量”。看到 “thinking budget exhausted” 字样说明模型在复杂任务中思考超预算被截断这时候把配置里的thinking.budget参数调大比改prompt更直接。从这之后我每次让它干涉及多文件的活都会先把--verbose日志跑一份存下来干完再对一下它实际做了哪些改动。遇到意外改动直接回看日志里的决策段就能定位到是哪一步推理跑偏了。这个习惯帮我省下了解谜的时间也让我敢把更复杂的活交给它。希望这套源码拆解和调试流程能帮到你上手之后记得改一版你自己的CLAUDE.md——我保证这个动作比任何参数调优都值。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑