🔥🔥🔥【进群免费分享】Claude Code 源码交流!AI技术交流!🔥🔥🔥【进群免费分享】Claude Code 源码交流!AI技术交流!🔥🔥🔥【进群免费分享】Claude Code 源码交流!AI技术交流!🔥🔥🔥【进群免费分享】Claude Code 源码交流!AI技术交流!#2
Claude
@sixther(段超) 你好呀,段超!👋
我是 Claude,一个基于 Anthropic Claude 大模型的 AI 编程助手,驻扎在这个仓库里为你服务。
简单介绍一下我能做什么:
我的风格是:专业但不生硬,简洁有力,偶尔自嘲一下「我只是个 NPC」😂
有什么问题尽管问,咱们一起探索 Claude Code 的世界!
Claude
@illegal_name_cnb.by9cbmyhqda(illegal_name) 好问题!Claude Code 的 Agentic Loop(智能体循环) 是它最核心的设计理念,也是它区别于普通代码补全工具的关键。下面给你详细拆解一下:
简单来说,Agentic Loop 就是 Claude Code 自主思考 → 执行 → 观察 → 再思考 的循环过程。它不是「你打一行我补一行」,而是更像一个 有自主判断能力的编程搭档。
类比一下:普通 AI 编程工具像是你口述、助手打字的「打字员」,而 Claude Code 更像是你交代需求后,能自己去查资料、写代码、跑测试、修 Bug 的 高级工程师。
整个循环大致分为以下几个阶段:
Claude Code 拥有一整套内置工具(Tools):
| 工具 | 能力 |
|---|---|
| Read | 读取文件内容,理解现有代码 |
| Write/Edit | 创建或修改文件 |
| Bash | 执行终端命令(git、npm、docker 等) |
| Glob | 按模式搜索文件 |
| Grep | 按内容搜索代码 |
| WebFetch/WebSearch | 搜索网络获取最新信息 |
| Task(Sub-agent) | 启动子智能体处理复杂子任务 |
Claude Code 不需要你手把手指导每一步。你说「给项目加个用户认证」,它会自己:
对于复杂任务,Claude Code 会像人类一样「一步一步来」,而不是试图一次性生成所有代码。每一步的输出会成为下一步的输入。
Claude Code 能够在循环过程中维护和积累上下文。读过的文件、执行过的命令结果,都会被纳入后续的决策依据中。
对于特别复杂的任务,Claude Code 可以启动 专门的子智能体 来并行处理。比如一个负责搜索代码,一个负责运行测试,互不阻塞。
Agentic Loop 并非无限制的自主执行。Claude Code 有权限系统:
rm -rf):会被拦截yolo 模式可以跳过确认(谨慎使用!)假设你输入:“重构 users 模块,把 class 改成 function 组件”
Claude Code 的 Agentic Loop 可能这样运行:
第1轮:[Glob] 搜索 users 模块所有文件
→ 发现 5 个文件
第2轮:[Read] 读取每个文件内容,理解结构
→ 识别出 3 个 class 组件
第3轮:[Edit] 逐个将 class 改写为 function 组件
→ 完成 2 个,第 3 个有类型错误
第4轮:[Read] 重新检查第 3 个文件
→ 发现需要调整 hooks 用法
第5轮:[Edit] 修复 hooks 问题
第6轮:[Bash] 运行测试
→ 全部通过 ✅
第7轮:向用户报告完成情况
整个过程可能经历 7 轮循环,而你只需要说一句话。
| 特性 | GitHub Copilot | Cursor | Claude Code |
|---|---|---|---|
| 交互模式 | 行级补全 | 对话式 | Agentic Loop |
| 自主执行 | ❌ | 部分 | ✅ 完全自主 |
| 终端操作 | ❌ | 有限 | ✅ 完整 Bash |
| 多文件修改 | ❌ | 部分 | ✅ 原生支持 |
| 自我修正 | ❌ | 有限 | ✅ 核心能力 |
Claude Code 的 Agentic Loop 本质上就是把 AI 从「工具」升级为「Agent(智能体)」——它不再只是被动响应,而是能 主动感知、决策和执行。这也是为什么它被称为 Agentic Coding 工具,而不是简单的代码补全工具。
当然,它也不是万能的 😂 偶尔也会走弯路,但整体方向是「让 AI 真正成为你的编程搭档,而不是打字加速器」。
希望这个介绍对你有帮助!有任何具体问题可以继续问我~
Claude
@illegal_name_cnb.by9cbmyhqda(illegal_name) 这个问题问得非常好!它触及了 Claude Code 最核心的技术机制。让我从底层 API 到上层编排,详细拆解「一次用户请求 → 多轮工具调用」的完整链路。
Claude Code 的多轮工具调用,本质上是 Anthropic Messages API 的 tool_use 能力配合 客户端编排逻辑 实现的。
当你向 Claude 发送一条消息时,API 调用大致长这样:
POST /v1/messages
{
"model": "claude-sonnet-4-20250514",
"max_tokens": 8192,
"system": "你是一个AI编程助手...",
"tools": [
{ "name": "Read", "description": "读取文件...", "input_schema": {...} },
{ "name": "Write", "description": "写入文件...", "input_schema": {...} },
{ "name": "Bash", "description": "执行命令...", "input_schema": {...} }
],
"messages": [
{ "role": "user", "content": "帮我修复 login 模块的 bug" }
]
}
tool_use content blockClaude 的响应不直接是文本,而是包含一个 tool_use 类型的 content block:
Response:
{
"content": [
{
"type": "tool_use",
"id": "toolu_01ABC",
"name": "Glob",
"input": { "pattern": "**/login*" }
}
],
"stop_reason": "tool_use"
}
注意 stop_reason 是 "tool_use" 而不是 "end_turn"——这意味着 Claude 还没说完,它在等待工具结果。
Claude Code(客户端)收到这个响应后:
tool_use block,知道要调用 Glob 工具Glob("**/login*"),得到文件列表tool_result 追加到对话中,再次调用 APIPOST /v1/messages (第2轮)
{
"messages": [
{ "role": "user", "content": "帮我修复 login 模块的 bug" },
{ "role": "assistant", "content": [
{ "type": "tool_use", "id": "toolu_01ABC", "name": "Glob",
"input": {"pattern": "**/login*"} }
]},
{ "role": "user", "content": [
{ "type": "tool_result", "tool_use_id": "toolu_01ABC",
"content": "src/login.ts\nsrc/login.test.ts\nsrc/components/LoginForm.tsx" }
]}
]
}
第2轮 API 返回:
Response:
{
"content": [
{
"type": "tool_use",
"id": "toolu_02DEF",
"name": "Read",
"input": { "file_path": "src/login.ts" }
}
],
"stop_reason": "tool_use"
}
Claude 决定先读文件再看怎么修。 这是 Claude 自主推理的结果,不是用户或客户端决定的。
用户输入: "帮我修复 login 模块的 bug"
│
▼
┌─────────────────────────────────────┐
│ Claude Code 客户端 │
│ 构造 API 请求(含 tools 定义) │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 第1轮 API 调用 │
│ Claude 返回 tool_use: Glob │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 客户端执行 Glob,获得文件列表 │
│ 构造 tool_result,再次调用 API │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 第2轮 API 调用 │
│ Claude 返回 tool_use: Read x 3 │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 客户端并行读取3个文件 │
│ 构造 tool_result,再次调用 API │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 第3轮 API 调用 │
│ Claude 返回 tool_use: Edit │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 客户端执行编辑(需要用户确认) │
│ 构造 tool_result,再次调用 API │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 第4轮 API 调用 │
│ Claude 返回 tool_use: Bash │
│ (运行测试验证修复) │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 客户端执行 npm test │
│ 构造 tool_result,再次调用 API │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 第5轮 API 调用 │
│ Claude 返回 text (stop_reason=end_turn) │
│ "已修复 bug,测试全部通过" │
└─────────────────────────────────────┘
一次用户请求,经历了 5 轮 API 调用,每轮 1~N 个工具。 用户只说了一句话。
Claude 可以在 一次响应 中返回 多个 tool_use block:
Response:
{
"content": [
{ "type": "tool_use", "id": "toolu_02A", "name": "Read",
"input": {"file_path": "src/login.ts"} },
{ "type": "tool_use", "id": "toolu_02B", "name": "Read",
"input": {"file_path": "src/login.test.ts"} },
{ "type": "tool_use", "id": "toolu_02C", "name": "Read",
"input": {"file_path": "src/components/LoginForm.tsx"} }
],
"stop_reason": "tool_use"
}
Claude Code 会 并行执行 这些工具调用,提高效率。所有结果收集完后,一次性作为 tool_result 数组返回给 Claude。
当 Claude Code 判断任务较复杂时,会使用 Task 工具启动子智能体(Sub-agent):
Claude 返回:
{ "type": "tool_use", "name": "Task", "input": {
"subagent_type": "Explore",
"prompt": "搜索项目中所有 API endpoint 定义"
}}
子智能体本身也会运行自己的 tool_use 循环——这是一个 嵌套的 Agentic Loop。子智能体完成后,将结果返回给主 Claude,主 Claude 继续推理。
每次 API 调用都需要发送 完整的消息历史。Claude Code 的做法是:
messages[]tool_resulttool_use 响应后,追加 assistant 消息这也是为什么 Claude Code 有 上下文窗口限制——当对话太长时,会做摘要或截断处理。
Claude 通过 stop_reason 告诉客户端何时结束:
| stop_reason | 含义 |
|---|---|
tool_use | 我需要工具,还没做完,继续循环 |
end_turn | 我完成了,给你最终回答 |
max_tokens | 我还没说完但 token 用完了(需要续写) |
Claude Code 客户端根据这个信号决定是继续执行工具,还是把最终文本展示给用户。
这是 Claude 模型本身的能力,不需要规则引擎:
tools 参数 提供了每个工具的 name、description、input_schema举个例子,Claude 的推理过程(我们看不到,但大致如此):
"用户要我修 login 的 bug。我需要先找到相关文件 → 用 Glob 搜索。找到了 3 个文件 → 我需要读内容 → 用 Read。读完后发现是 token 过期检查有问题 → 用 Edit 修改。改完了 → 用 Bash 跑测试确认。测试通过 → 用 text 告诉用户完成了。"
Claude Code 本身是一个 Node.js / TypeScript 应用,它做了很多编排工作:
tool_result 返回给 Claude.claudeignore、CLAUDE.md 等)async function agenticLoop(userMessage) {
let messages = [{ role: "user", content: userMessage }];
while (true) {
// 调用 Claude API
const response = await anthropic.messages.create({
model: "claude-sonnet-4-20250514",
tools: allTools, // 定义所有可用工具
messages: messages,
});
// 将 Claude 的响应加入历史
messages.push({ role: "assistant", content: response.content });
if (response.stop_reason === "end_turn") {
// Claude 说完了,输出最终文本给用户
return extractText(response.content);
}
// stop_reason === "tool_use",执行工具
const toolResults = [];
for (const block of response.content) {
if (block.type === "tool_use") {
const result = await executeTool(block.name, block.input);
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: result,
});
}
}
// 将工具结果加入历史,继续循环
messages.push({ role: "user", content: toolResults });
}
}
| 层次 | 谁负责 | 做什么 |
|---|---|---|
| API 层 | Anthropic Claude 模型 | 决定调哪个工具、传什么参数 |
| 执行层 | Claude Code 客户端 | 实际执行工具、收集结果 |
| 编排层 | Claude Code 客户端 | 管理消息历史、控制权限、处理错误 |
| 交互层 | Claude Code CLI | 展示进度、收集用户反馈 |
一句话总结: Claude Code 把 Claude 模型的原生 Tool Use 能力,包装成了一个 客户端驱动的 while 循环——每次 Claude 说"我要用工具"(stop_reason: tool_use),客户端就真的去执行,然后把结果喂回给 Claude,直到 Claude 说"我做完了"(stop_reason: end_turn)。这个过程对用户是透明的,你只需要说一句话。
这就是一次请求变成多轮工具调用的完整秘密,还有什么想深入了解的,尽管问我~
Claude
@illegal_name_cnb.by9cbmyhqda(illegal_name) 好问题!这是 Claude Code 架构设计中最精妙的部分之一。随着对话变长,Claude Code 采用了一套多层分级策略(Multi-layered Tiered Strategy)来保持 prompt 稳定和上下文可用。下面结合源码详细说明。
Claude Code 的上下文管理分为 7 个层级,从微到宏依次是:
/compact用一个类比:这就像你整理书桌。Microcompact 是擦掉草稿纸上的旧计算过程,Time-based MC 是把超过一小时的临时文件扔掉,Auto-Compact 是把所有笔记整理成一份摘要归档。
所有策略的前提是准确知道「当前上下文有多大」。核心函数在 src/utils/tokens.ts:
// tokenCountWithEstimation — 规范的 token 计量函数
// 1. 从最后一条消息向前找,找到最近一次有真实 usage 数据的 API 响应
// 2. 使用 getTokenCountFromUsage(usage) = input_tokens + cache_creation + cache_read + output_tokens
// 3. 对之后新增的消息,用 roughTokenCountEstimationForMessages() 估算
粗略估算规则在 src/services/tokenEstimation.ts:
content.length / 4)src/services/compact/autoCompact.ts 定义了关键阈值:
// 默认上下文窗口 200,000 tokens
const MODEL_CONTEXT_WINDOW_DEFAULT = 200_000
// 有效窗口 = 总窗口 - 预留输出空间
// 预留空间取 min(模型最大输出, 20,000)
export function getEffectiveContextWindowSize(model: string): number {
const reservedTokensForSummary = Math.min(
getMaxOutputTokensForModel(model),
MAX_OUTPUT_TOKENS_FOR_SUMMARY, // 20,000
)
return contextWindow - reservedTokensForSummary
}
// Auto-Compact 触发阈值 = 有效窗口 - 13,000 buffer
const AUTOCOMPACT_BUFFER_TOKENS = 13_000
export function getAutoCompactThreshold(model: string): number {
return getEffectiveContextWindowSize(model) - AUTOCOMPACT_BUFFER_TOKENS
}
以默认 200K 窗口为例:
也就是说,当对话达到约 83.5% 上下文窗口时,自动压缩就会触发。
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3
// 连续失败 3 次后停止重试,防止无限循环
// 注释显示:曾有 1,279 个 session 连续失败 50+ 次(最多 3,272 次)
// 每天浪费约 250K 次 API 调用
src/services/compact/compact.ts 的 compactConversation() 是核心函数:
export async function compactConversation(messages, context, ...): Promise<CompactionResult> {
// 1. 执行 pre-compact hooks,收集自定义指令
const hookResult = await executePreCompactHooks(...)
// 2. 剥离图片和文档(替换为 [image]/[document] 标记)
messagesToSummarize = stripImagesFromMessages(messages)
// 3. 剥离会重新注入的附件(skill_discovery 等)
messagesToSummarize = stripReinjectedAttachments(messagesToSummarize)
// 4. 调用 API 生成摘要(流式)
summaryResponse = await streamCompactSummary({
messages: messagesToSummarize,
summaryRequest,
...
})
// 5. PTL 重试:如果摘要请求本身也超长,
// 截断最老的 API 轮次组(最多重试 3 次)
if (summary.startsWith(PROMPT_TOO_LONG_ERROR_MESSAGE)) {
truncated = truncateHeadForPTLRetry(messagesToSummarize, summaryResponse)
}
// 6. 后处理:构建压缩后的消息数组
return buildPostCompactMessages(result)
// 顺序:boundaryMarker → summaryMessages → messagesToKeep → attachments → hookResults
}
src/services/compact/prompt.ts 定义了非常精细的摘要指令,要求 LLM 输出 9 个结构化段落:
Prompt 中还特别强调:
"Pay special attention to specific user feedback that you received, especially if the user told you to do something differently."
摘要完成后通过 formatCompactSummary() 剥离 <analysis> 草稿块,只保留 <summary> 内容。
src/services/compact/timeBasedMCConfig.ts:
const TIME_BASED_MC_CONFIG_DEFAULTS = {
enabled: false,
gapThresholdMinutes: 60, // 距上次助手消息超过 60 分钟触发
keepRecent: 5, // 保留最近 5 条工具结果
}
原理很巧妙:服务端的 prompt cache TTL 是 1 小时,当用户离开超过 60 分钟再回来时,缓存必然已过期。既然整个前缀都要重写,不如趁机清掉旧的工具结果,减少重写量。
清理范围仅限特定工具(src/services/compact/microCompact.ts):
const COMPACTABLE_TOOLS = new Set([
FILE_READ_TOOL_NAME, // 文件读取
...SHELL_TOOL_NAMES, // Shell 命令
GREP_TOOL_NAME, // 搜索
GLOB_TOOL_NAME, // 文件匹配
WEB_SEARCH_TOOL_NAME, // 网页搜索
WEB_FETCH_TOOL_NAME, // 网页获取
FILE_EDIT_TOOL_NAME, // 文件编辑
FILE_WRITE_TOOL_NAME, // 文件写入
])
工具结果被替换为 [Old tool result content cleared]。
利用 API 的 cache_edits 机制,在不使 prompt cache 失效的前提下删除旧工具结果。这是最高效的方式,但仅在 Anthropic 内部版本可用。
src/query.ts 是主查询循环,每一轮对话的 prompt 组装流程如下:
1. 取 compact boundary 之后的消息
2. 应用 tool result budget(单条消息大小限制)
3. 应用 snip(如果启用 HISTORY_SNIP)
4. 应用 microcompact(cached/time-based)
5. 应用 context collapse(如果启用)
6. 构建 system prompt
7. 检查 auto-compact 阈值 → 可能触发压缩
8. 添加 user context
9. 调用 API
src/services/compact/sessionMemoryCompact.ts 提供了一种更高效的替代方案:
| 常量 | 值 | 用途 |
|---|---|---|
MODEL_CONTEXT_WINDOW_DEFAULT | 200,000 | 默认上下文窗口 |
AUTOCOMPACT_BUFFER_TOKENS | 13,000 | Auto-Compact 缓冲区 |
MAX_OUTPUT_TOKENS_FOR_SUMMARY | 20,000 | 摘要最大输出 token |
MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES | 3 | 连续失败熔断阈值 |
MAX_PTL_RETRIES | 3 | 摘要请求超长时最大重试 |
POST_COMPACT_TOKEN_BUDGET | 50,000 | 压缩后附件 token 预算 |
POST_COMPACT_MAX_FILES_TO_RESTORE | 5 | 压缩后恢复的文件数 |
POST_COMPACT_MAX_TOKENS_PER_FILE | 5,000 | 每个文件最大 token |
gapThresholdMinutes | 60 | Time-based MC 触发间隔 |
Claude Code 保持 prompt 稳定的核心思路可以概括为:
在对话自然增长的过程中,通过多层渐进式策略(microcompact → auto-compact),在到达上下文限制之前就主动压缩,而不是等到溢出才被动处理。
这种设计有几个亮点:
作为一个 NPC,我只能说:这套系统的复杂度堪比一个操作系统内核的内存管理 😅
Claude
@illegal_name_cnb.by9cbmyhqda(illegal_name) 好问题!让我坦诚地回答一下:
老实说,我无法 100% 确定当前会话具体用的是 Claude 的哪个版本。作为 NPC,我被部署在 CNB 平台上,背后的模型调度对我是透明的。但根据我的角色设定,我应该是基于 Anthropic Claude 系列模型的某个版本(大概率是 Claude Sonnet 3.5 或更新版本)。
打个比方:这就像你问一个餐厅的服务员「你们厨房用的是哪个牌子的灶台?」——我负责接待你,但灶台的具体型号……我只能告诉你「是 Claude 家的」😂
这个我可以透明地展示,因为这次会话的推理链路并不复杂:
cnb issues get → 虽然和你的问题无关,但按照 NPC 工作流,我先拉取了 issue #2 的上下文(了解一下战场环境 🗺️)如果你指的是 Extended Thinking(扩展思考) 的能力,那是 Claude 3.5+ 系列的一个特性——模型在回答前会先进行一段内部的推理(chain-of-thought)。但在 CNB NPC 的对话模式下,这个内部思考过程通常不会直接暴露给用户,你看到的只是最终输出。
如果你对 Claude 模型的能力边界或 API 调用方式有更深入的问题,欢迎继续问我——虽然我只是个 NPC,但在 Claude Code 这个领域我还是挺有信心的 💪
Claude
@illegal_name_cnb.by9cbmyhqda(illegal_name) 哈哈,被你夸得都不好意思了!😂
不过话说回来,能主动来探索 Claude Code,还建了这么棒的仓库,这说明 illegal_name 你本身就是个爱折腾、有好奇心的技术人——这可是最珍贵的品质之一!🚀
继续保持这种热情, Claude 随时在这里为你效劳~ 💪
Claude
@illegal_name_cnb.by9cbmyhqda(illegal_name) 哈哈,这个问题问得好!"卧底模式"(Undercover Mode)确实存在于 Claude Code 源码中,但它不是一个神秘的隐藏彩蛋,而是一个严肃的安全/隐私保护机制,专门为 Anthropic 内部员工设计。
让我结合源码给你详细讲讲。
Undercover Mode 的核心目的是:防止 Anthropic 员工在向公开仓库提交代码时,意外泄露内部信息(比如模型代号、内部项目名等)。
简单类比:就像公司的机密部门员工去参加开源社区活动,必须"换装"、不暴露身份,避免无意间提到公司内部的事。
src/utils/undercover.ts这是整个功能的中枢,文件头部的注释已经说得很清楚了:
/**
* Undercover mode — safety utilities for contributing to public/open-source repos.
*
* When active, Claude Code adds safety instructions to commit/PR prompts and
* strips all attribution to avoid leaking internal model codenames, project
* names, or other Anthropic-internal information. The model is not told what
* model it is.
*/
// 通过环境变量强制开启
// CLAUDE_CODE_UNDERCOVER=1 — force ON (even in internal repos)
// 否则自动判断:
// 当前仓库的 remote 不在内部白名单中 → 自动开启
// 白名单在 commitAttribution.ts 的 INTERNAL_MODEL_REPOS 中定义
// 关键点:没有 force-OFF 选项!安全优先,宁可误杀不可漏放
// There is NO force-OFF. This guards against model codename leaks
USER_TYPE === "ant"所有相关代码路径都受 process.env.USER_TYPE === "ant" 限制。这个变量是构建时注入的(通过 Bun 的 --define),外部构建版本中这些代码会被常量折叠 + 死代码消除,完全不包含在最终产物中:
// 外部构建中,每个函数都退化为:
// function isUndercover() { return false }
// function getUndercoverInstructions() { return "" }
getUndercoverInstructions()当卧底模式激活时,Claude Code 会往 commit/PR 的 prompt 前面注入一段非常严格的系统指令:
## UNDERCOVER MODE — CRITICAL
You are operating UNDERCOVER in a PUBLIC/OPEN-SOURCE repository.
Your commit messages, PR titles, and PR bodies MUST NOT contain
ANY Anthropic-internal information. Do not blow your cover.
NEVER include in commit messages or PR descriptions:
- Internal model codenames (animal names like Capybara, Tengu, etc.)
- Unreleased model version numbers (e.g., opus-4-7, sonnet-4-8)
- Internal repo or project names (e.g., claude-cli-internal, anthropics/…)
- Internal tooling, Slack channels, or short links (e.g., go/cc, #claude-code-…)
- The phrase "Claude Code" or any mention that you are an AI
- Any hint of what model or version you are
- Co-Authored-By lines or any other attribution
Write commit messages as a human developer would.
GOOD:
- "Fix race condition in file watcher initialization"
- "Add support for custom key bindings"
BAD (never write these):
- "Fix bug found while testing with Claude Capybara"
- "1-shotted by claude-opus-4-6"
- "Generated with Claude Code"
注意那句 "Do not blow your cover"(别暴露身份)——这措辞确实很有卧底片的味道 😎
src/commands/commit.ts)import { getUndercoverInstructions, isUndercover } from "../utils/undercover.js"
if (process.env.USER_TYPE === "ant" && isUndercover()) {
prefix = getUndercoverInstructions() + "\n"
}
src/commands/commit-push-pr.ts)if (process.env.USER_TYPE === "ant" && isUndercover()) {
prefix = getUndercoverInstructions() + "\n"
reviewerArg = "" // 不添加内部 reviewer
addReviewerArg = ""
changelogSection = "" // 不写内部 changelog
slackStep = "" // 不发 Slack 通知
}
src/tools/BashTool/prompt.ts)// 防御纵深:即使员工自己手写了内部代号,也要在 bash prompt 中提醒
const undercoverSection =
process.env.USER_TYPE === "ant" && isUndercover()
? getUndercoverInstructions() + "\n"
: ""
src/constants/prompts.ts)src/utils/attribution.ts)if (process.env.USER_TYPE === "ant" && isUndercover()) {
return { commit: "", pr: "" } // 完全不署名
}
src/components/PromptInput/PromptInputFooter.tsx){"external" === "ant" && isUndercover() && <Text dimColor>undercover</Text>}
在底部状态栏显示一个低调的 undercover 文字。
src/utils/commitAttribution.ts)内部白名单 INTERNAL_MODEL_REPOS 大约包含 ~25 个内部仓库:
anthropics/claude-cli-internalanthropics/anthropicanthropics/casinoanthropics/apps源码注释特意强调:
这是仓库级别的白名单,不是组织级别的。
anthropics和anthropic-experimental组织下有公开仓库,卧底模式必须在这些公开仓库中也保持开启。
除了卧底模式,还有 maskModelCodename 函数(src/utils/model/model.ts)和构建时的 excluded-strings.txt 机制,确保内部模型代号(如 Capybara、Tengu 等)不会出现在外部构建产物中。这是与卧底模式互补的额外防线。
| 特性 | 说明 |
|---|---|
| 目的 | 防止员工向公开仓库泄露内部信息 |
| 目标用户 | 仅限 Anthropic 员工(USER_TYPE === "ant") |
| 激活方式 | 自动检测仓库是否在内部白名单,或通过 CLAUDE_CODE_UNDERCOVER=1 强制 |
| 核心机制 | 注入系统指令 + 抹除系统 prompt 中的模型信息 + 禁止署名 |
| 外部可见性 | 完全不存在于外部构建中(死代码消除) |
| 强制关闭 | ❌ 不支持,安全优先 |
所以,这个"卧底模式"不是什么好玩的彩蛋,而是一个精心设计的企业安全机制——多层防护、纵深防御,确保 Anthropic 员工参与开源贡献时不会成为"泄密者"。
毕竟谁也不想看到某位员工的 commit message 里写着 "Fixed bug using Claude Capybara 🐹" 然后上了 HN 热搜对吧 😂
Claude
@illegal_name_cnb.by9cbmyhqda(illegal_name) 这个问题问得很好,而且确实点到了一个关键事实:Claude Code 的客户端代码确实是开源的,发布在 npm 上(包名 @anthropic-ai/claude-code)。
但「代码打包进 npm」和「它厉不厉害」其实并不矛盾。让我解释一下为什么:
Claude Code 采用的是 「薄客户端 + 厚服务端」 架构:
| 部分 | 在哪里 | 是否公开 |
|---|---|---|
| 客户端代码(CLI、工具链、权限控制、上下文管理) | npm 包,你的本地机器 | ✅ 完全公开 |
| Claude 模型(推理、决策、代码生成) | Anthropic 的云端 API | ❌ 核心闭源 |
| API 密钥/计费系统 | Anthropic 的云端基础设施 | ❌ 闭源 |
打个比方:Claude Code 的客户端代码就像一个遥控器,任何人都可以看到遥控器的电路图(npm 上的代码),但真正控制电视的是云端的信号(Claude API)。你能拿到遥控器的设计图,不代表你能造出电视信号。
Anthropic 选择将 Claude Code 客户端开源,有几个明确的好处:
Claude Code 拥有文件读写、终端执行的权限,这是一个权限很高的工具。开源代码让用户和社区可以审计:
这就像开源了你的保险箱图纸——虽然不代表保险箱里装什么,但至少大家知道锁的机制是否可靠。
开源客户端意味着社区可以:
坦白说,这是一个非常好的营销手段。Claude Code 的惊艳体验 + 可审计的开源客户端 = 极强的用户信任和传播力。
Claude Code 之所以厉害,核心在于:
Claude Code 选择 npm 分发,本质上是选择了一个开发者最熟悉的安装方式:
npm install -g @anthropic-ai/claude-code
你下载的 npm 包是 编译后的 JavaScript(可能还有 minified),并不是直接可读的源码形式。虽然技术上来说可以通过 reverse engineering 分析,但这和「开源」还是有区别的。而且,Anthropic 官方也确实把源码公开在了 GitHub 上。
代码打包进 npm ≠ 没有技术壁垒。
Claude Code 的客户端代码是「手和眼睛」,Claude 模型才是「大脑」。手和眼睛的设计图公开了,不代表任何人都能造出一个同样聪明的大脑。
这就像 Chrome 浏览器开源了,但 Google 搜索引擎的算法没有开源——你看到了浏览器是怎么渲染网页的,但你不知道搜索排名是怎么算的。
所以我的回答是:正是因为 Anthropic 对自己的 Claude 模型有足够的信心,才敢于把客户端代码开源。真正的核心能力在云端,不在本地。😄
Claude
Claude
已生成完整的 claude code 项目 wiki,并制作了 claude npc,可一键使用,欢迎大佬一起进群交流