简介:Claude Code 源码是一份面向 AI 编程工具研究者、前端/Node.js 开发者及开源爱好者的完整代码仓库,适合用于学习现代 CLI 应用架构、模块化拆分与类型安全实践。资源共 1903 个文件,以 1332 个 TypeScript 文件与 552 个 TSX 组件文件为主,辅以少量 JS 工具脚本和 1 份 Markdown 说明文档,压缩包约 9.43MB,目录结构清晰,便于按功能模块检索阅读。目前已有 286 人浏览学习。源码不仅覆盖核心算法与数据处理逻辑,还包含详尽注释、测试用例、开发者指南、API 文档和用户手册,能够帮助读者理解大型开源项目如何通过 Git 管理版本、组织模块和保障代码质量。对于希望提升工程能力、研究 Agent 类工具实现原理或参与社区贡献的开发者而言,这份源码提供了难得的参考资料。
1. 找不到源码的“Claude Code源码”:终端AI代理值得啃的其实是四层内核
把“Claude Code源码”当成一个能直接下载的仓库去找,多半会扑空。Claude Code 是 Anthropic 出品的终端 AI 编码代理,核心并不开源,网上流传的各种“源码”也基本都是包装脚本或者二次封装。可我更愿意把“源码”理解成另一件事:这个工具靠什么逻辑跑起来、改了哪里能改变它的行为、出了问题从哪一层开始查。实际操作下来,它的可观察内核一共四层:磁盘上的配置目录、工具调用循环、钩子与 MCP 扩展点、以及会话日志。这四层不需要你读过一行闭源代码,也能照样定位问题、加能力,甚至照着复刻一个最小可用版本。适合谁读呢:想基于 Claude Code 做二次开发的人,和那些在“源码”里翻半天却连配置目录都找不到的新手。
2. 先拆磁盘:~/.claude 与项目 .claude 目录就是离你最近的“源码”
所有闭源终端工具最诚实的那份“源码”,就是它写在磁盘上的配置与状态。Claude Code 把全局状态放在~/.claude,项目级设置放在.claude/,这两片目录看明白了,剩下的运行逻辑全是顺着它们展开的。
2.1 全局层:~/.claude 里值得先读的六个文件
完成 Claude Code 安装、跑过一次会话之后,主目录下会出现~/.claude。别急着删,这一目录就是终端工具“源码级”的现场。真正值得读的六个东西我按排查频率排个序:
~/.claude.json:账号级状态文件,注意它是个文件,不在~/.claude目录里面。它记录已登录账号、projects 列表、每个项目的 git 仓库路径与运行次数。我一般说它是“户口本”,后面排查权限和上下文污染都用得上。~/.claude/settings.json:全局配置,权限、环境变量、钩子都允许在这里出现。它的典型长这样:
{ "env": { "CLAUDE_CODE_MAX_OUTPUT": "12000" }, "permissions": { "allow": ["Bash(npm test:*)", "Read(.*)", "Edit(.*)"], "deny": ["Bash(rm -rf:*)", "Write(.*/secrets.*)"] } }逻辑说明:env里的变量会当启动参数注入子进程;permissions.allow是白名单,deny是黑名单。我习惯把“防止误删”这类规则写进全局deny,因为它真的救过我一次,细节在第 5 章展开。参数说明:allow里每一项的语法是工具名(正则模式),比如Bash(npm test:*)只在命令以npm test:开头时免确认。这种精确授权法比直接写Bash(.*)稳得多,权限检查是顺序匹配,先看 deny 再看 allow,通配符越多越容易给出意想不到的放行。
~/.claude/logs/:会话日志目录,文件以SL_开头,例如SL_<时间戳>.jsonl。这是我觉得最接近“源码运行时”的东西,每一轮工具调用、HTTP 请求、token 统计都在里面。~/.claude/hooks/:全局钩子的落点。~/.claude/commands/:全局个性化指令,也就是/命令名。~/.claude/CLAUDE.md:全局记忆文件,每次会话都会注入,适合写跨项目的通用约束。
这里有个从源码视角特别容易误会的点:~/.claude.json和~/.claude/是两回事,前者是文件、后者是目录,备份和导出时要分开处理。我见过同事把.claude.json当成目录删了,登录态全丢,全部项目历史记录一起没。
提示:
~/.claude.json里可能包含与账号相关的敏感信息,提交到 git 仓库前务必确认已经被.gitignore排除。
2.2 项目层:.claude/ 与 CLAUDE.md 的加载顺序
进到项目根目录,Claude Code 会把.claude/当成项目级“源码目录”。常见的结构是这样:
.claude/ ├── settings.json # 项目级权限与 env ├── settings.local.json # 个人本地覆盖,不进 git ├── .mcp.json # MCP 服务器声明 ├── commands/ │ └── review.md # 自定义指令 /review ├── hooks/ │ └── post_tool_use.sh # 工具后置钩子 └── CLAUDE.md # 项目说明文档settings.local.json是最容易被忽略的一个点:它按机器生效,冲突时覆盖settings.json。我会把本地代理、私有 token 放这里,这样整个仓库继续提交给别人也不会泄露个人信息。它和settings.json的关系,类似 git 里config和config.local的分工。
CLAUDE.md的加载顺序是团队协作里反复出问题的点。实际顺序是:全局~/.claude/CLAUDE.md→ 仓库根目录CLAUDE.md→ 当前子目录的CLAUDE.md。也就是说,在子目录执行claude时,它会同时读多层。用表格看一眼就明白:
| 加载顺序 | 文件位置 | 作用域 | 典型用途 |
|---|---|---|---|
| 先读 | ~/.claude/CLAUDE.md | 全局 | 个人代码风格、跨项目禁用项 |
| 次读 | 仓库根CLAUDE.md | 项目 | 架构约定、构建命令、行为红线 |
| 后读 | 当前目录CLAUDE.md | 会话 | 当前模块的局部说明 |
一个关键理解:越后读的越靠近最终提示词,但这不代表“后读优先”。相反,经验上后加载的内容更容易把前两层覆盖掉。如果你发现模型突然像变了一个人,多半是根目录那份和子目录那份打架了,改其中任意一份前先确认是谁在生效。
2.3 会话层:/status 暴露的运行时状态
在会话里敲/status,可以得到当前会话快照:项目路径、模型、权限模式、当前目录,以及最近一次工具调用结果。这是我理解“源码可观察性”的第一现场。新手排查问题第一反应是去翻日志,我的习惯是先/status三秒钟,确认模型、路径和权限模式没有跑偏,再往下查。
/status里还有一个容易被忽略的点:它会显示当前会话的工作目录。很多人把 Claude Code 当成项目根目录专属工具,其实它在任意子目录都能启动,而“它以为自己身处哪里”直接决定了工具调用的相对路径。同一个命令在根目录和子目录启动,行为可能完全不同,这算是终端类 agent 特有的心智负担。
3. 运行时四件套:工具调用循环、上下文构建、日志落盘与进程权限
读出配置只是开始。Claude Code 的“源码级”行为,全在一个循环里:Claude 模型收到上下文 → 决定调用一个工具 → 工具执行并返回结果 → 结果附加进上下文 → 再来一轮。理解这个循环,就能解释 90% 的实际现象。
3.1 工具调用循环:六类内建工具与两次确认
Claude Code 内置工具大致分六类:读文件(Read)、编辑(Edit)、写文件(Write)、执行命令(Bash)、搜索代码与文件(Glob/Grep)、子任务(Task)。每次调用,如果权限没被白名单放过,终端会弹确认;半自动模式下你只要按回车就会通过,全自动模式下完全跳过。
用Bash举例子,常见的调用景观是:
# 让 Claude 运行测试并在失败时尽早停 pytest tests/ -x --tb=short --no-header逻辑说明:Bash工具默认以你的系统 shell 执行,输出截断进入上下文,失败时退出码也是可见信息。我建议在所有常用命令前加上--no-header或--quiet类似的参数,因为它能把“成功但不重要”的输出压下去,减少上下文膨胀。参数说明:-x让 pytest 在第一个失败处停住,--tb=short压缩错误回溯为几行,模型更容易把失败原因读出来。这一步不是玄学,是给上下文省字。
观察日志你会发现一个高频行为模式:Claude 做完批量修改之后,紧接着会调Read或Glob去复核,然后才进入下一轮计划。Edit 后面跟 Read,这个“写后读”节奏是复现它方案时最值得模仿的一点,自己写 agent 时保留这个习惯,能少犯一半低级错误。
3.2 上下文构建单元:CLAUDE.md 与工具结果如何拼进提示词
每次请求真正发给模型的,不只是你刚输入的那句话。常见的上下文组装顺序是:
- 系统提示,含安全约束与工具定义
~/.claude/CLAUDE.md全局记忆- 根目录和当前目录的
CLAUDE.md - 最近会话历史,带着工具调用结果
- 当前用户输入
当 token 逼近模型上限时,历史会被压缩:对最早几轮做摘要,较近的轮次保留原文。这个“压缩点”你看不到,但能感觉到——如果你发现 Claude 突然忘了三天前让它记住的某个路径,多半是历史被摘要吃掉,而不是它变笨。对应解法是把关键路径写回CLAUDE.md,因为文件内容在每次请求都会重新读取,比历史可靠得多。
这里有个值得专门强调的边界:压缩后的摘要是不透明的。你无法从日志直接还原“模型到底忘掉了哪句”。所以我的原则是:凡是“必须记住”的内容,不依赖历史,全放进CLAUDE.md;凡是“想起来才用”的内容,放进按需指令或者技能里。
3.3 日志落盘:SL_ 文件里能挖出什么
会话日志~/.claude/logs/SL_<时间戳>.jsonl是逐行的 JSON。大体是一个事件一行,常见能看到工具调用类型、请求方向、时间戳。我排查问题时最常用的三件事:
- 搜工具调用行,看某个工具是否真的被调用,以及调用顺序。
- 搜结果行,看工具输出是否异常截断。
- 搜 token 相关字段,看哪一轮把上下文吃爆了。
# 在最近一个会话日志里统计工具调用频率 cd ~/.claude/logs && ls -t SL_*.jsonl | head -1 | xargs grep -o '"name":"[A-Za-z_]*"' | sort | uniq -c | sort -rn逻辑说明:ls -t取最近修改的日志文件,grep -o提取工具名再统计频率,能快速看出当前会话里哪个工具是被反复用的,从而判断是不是陷入了某种死循环。参数说明:head -1取最新一份就够了;如果要跨会话统计,把head -1去掉,直接cat SL_*.jsonl再管道,代价是数据量大会拖慢统计速度。
日志还藏着权限相关的行为:本地权限规则是“先匹配 deny,再匹配 allow,都没有则弹窗”。我把这个顺序背下来以后,很多“为什么偏偏它要问”的问题就迎刃而解。如果是 deny 命中,它不弹窗而是直接拒绝;如果是 allow 命中,它不弹窗直接执行。只有两边都没命中才出现那个你熟悉的确认框。
3.4 进程模型:为什么编辑配置不热加载
Claude Code 的每次会话是一个常驻进程,配置只在启动时读一次。你在另一个终端改了settings.json,正在跑的会话不会热加载。这一条看着简单,实际踩坑率极高。很多人开着一个长会话,外面改了 MCP 配置,回来敲命令测试,发现新工具没出现,第一反应是“坏了”,第二反应是去翻网络配置,折腾一圈才发现是进程根本没重新读文件。
我在实际工作里的判断顺序是:先敲/status看当前会话状态,再敲对应的管理命令(比如 MCP 相关就敲/mcp),最后才决定要不要重启会话。盲重启十有八九会发现“状态根本没问题,是配置本身没写对”。
4. 扩展层就是可变源码:hooks、个性化命令与技能、MCP 接入
闭源软件找“源码”的意义,一半是为了改行为。Claude Code 给普通用户留的可变面是 hooks、个性化命令和 MCP——不动主程序,也能在工具调用前后插自己的判断。
4.1 hooks:在工具调用前后插一手的标准写法
hooks 配置在settings.json的hooks字段里,事件名我常用的有:
UserPromptSubmit:用户输入后、发给模型前触发PreToolUse:工具执行前触发PostToolUse:工具执行后触发Stop:Claude 完成一轮回复后触发Notification:通知回调PreCompact:历史压缩前触发
一个典型用法:在PreToolUse里拦截Bash,把危险路径挡在外面。
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "test \"$TOOL_INPUT\" = \"rm -rf /\" && exit 2 || exit 0" } ] } ] } }逻辑说明:PreToolUse的 hook 退出码非 0 表示拒绝该调用,0 表示放行。上面这个例子虽然粗暴,但能挡住最危险的那条命令。参数说明:matcher可以指定工具名,事件触发时TOOL_INPUT、TOOL_NAME、HOOK_EVENT_NAME这些环境变量会被自动填入,你的脚本可以读取它们做判断;hook 自身的输出会作为一条观察结果回到上下文,所以不用怕“写不出后台日志”。
在一个安全要求高的环境里,我会把所有Write(.*\.env)都通过 hook 拒绝,同时输出提示“请在 .env.example 里写模板”。这个逻辑放在代码层去做会漏,放在工具调用层做反而漏不了,因为它是绕不过去的必经之路。
4.2 个性化命令与技能:/命令名 的落点
在.claude/commands/下放一个.md文件,就能获得一个斜杠命令。文件名去掉.md就是命令名,文件内容的第一段描述会显示在命令面板,剩下的全是给 Claude 的指令。
--- description: 在提交前做一次上下文自检 --- 检查当前工作区未提交改动里是否存在调试代码(console.log / print / TODO); 如果有,先列出文件清单,逐个询问我是否删除; 全部处理完再输出一份改动摘要。逻辑说明:自定义命令本质上是一段可复用提示词,适合把高频动作固化成团队约定。参数说明:文件头的description会在命令面板里优先显示,建议写成“动词 + 对象”的结构,比如上面的“在提交前做一次上下文自检”,比单写“自检命令”好用得多。
技能(Skill)这类更高级的自动化,起手式也类似:先拆成一段可复用的指令文件,再逐步给配套脚本和资源目录。我见过不少人一上来就想做“全自动技能”,结果第一版就把所有逻辑塞进一条命令里,跑不通也难调试。更稳的做法是先把单文件命令跑顺,再从命令升级成技能。
4.3 MCP:把外部工具挂进工具调用循环
MCP 接入的本质,是让外部服务暴露成新的工具名。项目级配置写在.claude/.mcp.json,用claude mcp add命令也能登记。
{ "mcpServers": { "internal-docs": { "command": "npx", "args": ["-y", "@your-org/doc-mcp"], "env": { "DOC_BASE_URL": "https://docs.example.internal" } } } }一个关键认知:.mcp.json第一次导入后,工具定义会固化到会话,之后改文件不一定能热生效。常见现象是“我改了配置但新工具没出现”。我顺手排 MCP 故障的顺序是:先claude mcp list看注册状态,再打开.mcp.json检查命令路径,最后翻日志看握手是否成功。把这个顺序反过来的人,往往先怀疑网络,最后才发现是npx版本路径不对。
提示:MCP 服务器的日志通常不和主会话日志混在一起。先确认进程真的被拉起来了,再去查网络和鉴权,能省掉 80% 的排查时间。
5. 源码向使用中的五个避坑现场:现象、原因、解决
基于上面的机制,我把常见问题压成五条。每一条都是我在真实跑动中踩过或者看别人踩过的坑,按“现象 → 原因 → 解决”写。
5.1 权限拒绝:命令能在我终端跑,Claude 却说没有权限
现象:让 Claude 执行npm run build,它反复说需要授权,甚至出现权限相关的报错。原因:settings.json里 deny 规则写得太宽,或某一项规则的正则语法写错,权限检查直接抛异常而不是平滑降级。解决:先敲/status确认当前权限模式,再打开settings.json检查正则。我的原则是 deny 里只写确定不干的,allow 里让 Claude 精确到命令前缀,比如Bash(npm run build:*),而不是Bash(.*)。通配符太多,权限检查看起来优雅,实际上把安全兜底也删了。
5.2 钩子输出污染上下文:一条 stdout 让模型前后判若两人
现象:加了PostToolUse钩子之后,Claude 开始重复“我已经处理过”这种话,或者在某次大改后突然忘了之前的约定。原因:钩子的 stdout 会被当成工具结果注入上下文;如果钩子里有echo "ok"这类无意义输出,模型会把“ok”理解成一次成功信号,并在后续推理中引用它。解决:钩子的命令遵循“安静”原则——成功时输出为空,失败时只输出一行错误。调试钩子期间,我故意让命令打印当前事件名定位问题,定位完立刻删掉输出。
5.3 中途改配置不生效:同一个会话里摸不到新能力
现象:开了新 MCP 服务或者改了 hooks,当前会话里测试新命令永远失败。原因:Claude Code 进程启动时就把配置持久化在状态里,运行中不会重新读文件。解决:涉及 MCP、hooks、permissions 的修改,直接退出会话重进。我前面说过,重进前先确认配置本身没有写错,这个顺序比啥都重要。
5.4 CLAUDE.md 越长,约束越弱
现象:CLAUDE.md写到 300 行以后,模型开始频繁遗漏关键约束,甚至在犯错之后自我辩解。原因:长指令在信息密度上不如短指令,而且历史压缩发生时,靠后的约束最容易被摘要掉。解决:把CLAUDE.md拆成两层,根目录那份只留“行为红线”和“项目架构一句话描述”,具体的命名规范、目录说明挪进.claude/commands/里按需调用。本质上是把指令从常驻内存移到按需加载,压缩损失会小很多,这个做法我下一章给出完整方案。
5.5 会话日志里找不到想看的东西
现象:想定位一次模型回答为什么崩,翻SL_*.jsonl却没找到完整的请求负载。原因:日志默认是事件流,不是 HTTP 抓包,它记录“发生了什么”,不记录“完整请求体长什么样”。解决:别在日志里硬翻,复现问题时先开调试模式,把复现步骤固定下来,再把现象、日志片段、配置一起存下来。我一般用一个最小仓库,三句话描述复现步骤,再附上引发问题的工具调用日志,比漫无目的地翻日志效率高一个量级。
6. 进阶:把三千行 CLAUDE.md 瘦身成可按需加载的“指令源码”
高约束项目的终局,是把记忆文件做成“索引 + 按需加载”。做法是:根目录CLAUDE.md只保留四段——项目一句话、行为红线、目录索引、以及“遇到 X 请先读.claude/specs/下的 Y.md”。
# 项目 CLI 一句话:这是一个处理订单导出的 Node CLI,只允许操作 orders 表。 行为红线: - 不允许删除任何记录,只允许打 soft-delete 标记。 - 不允许直接改 prod 库,只允许通过迁移脚本。 按需加载: - 涉及订单状态机 → 先读 .claude/specs/order-state.md - 涉及导出格式 → 先读 .claude/specs/export-format.md 目录索引: - 业务代码在 src/modules - 测试在 tests/e2e逻辑说明:把原来的 3000 行拆成specs/下若干独立文件,CLAUDE.md退化成一张索引表。模型平时只扛着索引跑,真正遇到订单状态时才会去读order-state.md。这比把 3000 行全部塞进每次请求省下的 token 不是一点半点,而且压缩发生时索引本身的稳定性远高于长文。
配合 hooks,可以做成更自动的按需加载:在UserPromptSubmit事件里 grep 用户输入,如果命中“订单”“状态机”这类关键词,就把order-state.md的内容追加到本次上下文,再交回模型。这段逻辑用不到 20 行 bash 就能写完,但它把“CLAUDE.md 长了就不遵守”的毛病直接改成了“不长就不会不遵守”。
这套做法的边界我也说清楚:它依赖目录规整到“索引一句话能找到”。如果仓库本身结构混乱,索引写不好,反而会带偏模型。我会先用/status和日志确认模型当前是否真的在遵守指令,再做这个改造,别一上来就拆。
收个尾:我最常用的排错动作,永远是先/status、再翻SL_日志,最后才看代码——这个顺序帮我省下的时间,比任何一条配置技巧都多。希望帮到你。
本文还有配套的精品资源,点击获取