目录
  1. 1. 一、架构总览
  2. 2. 二、文件系统抽象层
    1. 2.1. 2.1 fsOperations.ts — 文件系统接口 (771行)
    2. 2.2. 2.2 file.ts — 文件操作工具集 (585行)
      1. 2.2.1. 核心函数清单
      2. 2.2.2. 原子写入机制 (writeFileSyncAndFlush_DEPRECATED)
      3. 2.2.3. 行号前缀系统
    3. 2.3. 2.3 fileReadCache.ts — 文件读取缓存
  3. 3. 三、Shell执行引擎
    1. 3.1. 3.1 Shell.ts — Shell执行核心 (475行)
      1. 3.1.1. Shell发现 (findSuitableShell())
      2. 3.1.2. 命令执行 (exec())
      3. 3.1.3. 关键安全设计
    2. 3.2. 3.2 ShellCommand.ts — 命令结果封装
    3. 3.3. 3.3 子进程环境隔离 (subprocessEnv.ts)
  4. 4. 四、Git操作系统
    1. 4.1. 4.1 git.ts — Git核心操作 (927行)
      1. 4.1.1. Git根目录发现 (findGitRoot)
      2. 4.1.2. 规范化根目录解析 (findCanonicalGitRoot)
      3. 4.1.3. 远程URL规范化 (normalizeGitRemoteUrl)
      4. 4.1.4. 其他Git操作
  5. 5. 五、Token计算与上下文管理
    1. 5.1. 5.1 tokens.ts — Token统计核心 (262行)
      1. 5.1.1. 关键函数架构
      2. 5.1.2. tokenCountWithEstimation() — 上下文窗口大小计算
      3. 5.1.3. getTokenCountFromUsage() — 完整上下文Token数
      4. 5.1.4. finalContextTokensFromLastResponse() — 预算计算专用
    2. 5.2. 5.2 context.ts — 上下文窗口管理 (222行)
      1. 5.2.1. 常量定义
      2. 5.2.2. getContextWindowForModel() — 模型上下文窗口解析
  6. 6. 六、配置管理系统
    1. 6.1. 6.1 config.ts — 配置核心 (1818行)
      1. 6.1.1. 两级配置架构
      2. 6.1.2. GlobalConfig 关键字段分类
      3. 6.1.3. 配置读写机制
      4. 6.1.4. 信任对话系统 (checkHasTrustDialogAccepted)
  7. 7. 七、认证系统
    1. 7.1. 7.1 auth.ts — 认证核心 (2003行)
      1. 7.1.1. 认证源优先级
      2. 7.1.2. OAuth Token管理
      3. 7.1.3. API Key Helper
      4. 7.1.4. 受管 OAuth 上下文
  8. 8. 八、搜索引擎 — Ripgrep集成
    1. 8.1. 8.1 ripgrep.ts (680行)
      1. 8.1.1. Ripgrep配置解析
      2. 8.1.2. 执行模式
      3. 8.1.3. 超时和终止策略
  9. 9. 九、缓存与记忆化
    1. 9.1. 9.1 memoize.ts (270行)
      1. 9.1.1. memoizeWithTTL — 带TTL的写穿缓存
      2. 9.1.2. memoizeWithTTLAsync — 异步版写穿缓存
      3. 9.1.3. memoizeWithLRU — LRU缓存
  10. 10. 十、沙箱安全系统
    1. 10.1. 10.1 sandbox/sandbox-adapter.ts (986行)
      1. 10.1.1. 沙箱原理
      2. 10.1.2. 路径模式解析
      3. 10.1.3. 权限到沙箱规则的转换
  11. 11. 十一、Diff引擎
    1. 11.1. 11.1 diff.ts (178行)
  12. 12. 十二、哈希与加密
    1. 12.1. 12.1 hash.ts
  13. 13. 十三、进程管理
    1. 13.1. 13.1 lockfile.ts — 延迟加载的文件锁
    2. 13.2. 13.2 cleanupRegistry.ts — 资源清理注册表
    3. 13.3. 13.3 sleep.ts — 可中止的休眠
  14. 14. 十四、错误处理体系
    1. 14.1. 14.1 errors.ts (239行)
      1. 14.1.1. 错误类层次
      2. 14.1.2. isAbortError() — 统一的中止检测
      3. 14.1.3. TelemetrySafeError 的设计哲学
  15. 15. 十五、设计哲学总结
【Claude Code源码剖析】15-核心工具函数库 (Utils)

源目录: src/utils/ — 400+ 文件,构成整个系统的基础设施层
职责: 文件I/O、Shell执行、Git操作、Token计算、配置管理、认证、搜索、缓存、沙箱、进程隔离


一、架构总览

Utils目录是 Claude Code 最庞大的模块,提供从底层文件操作到高层业务逻辑的完整工具链。按功能域分为以下子系统:

src/utils/
├── 文件系统层: file.ts, fsOperations.ts, fileRead.ts, fileReadCache.ts, diff.ts
├── Shell执行层: Shell.ts, ShellCommand.ts, shell/, sandbox/
├── Git操作层: git.ts, git/, gitDiff.ts, worktree.ts
├── Token管理层: tokens.ts, tokenBudget.ts, context.ts
├── 配置管理层: config.ts, settings/, configConstants.ts
├── 认证层: auth.ts, authPortable.ts, authFileDescriptor.ts, secureStorage/
├── 搜索层: ripgrep.ts, glob.ts, codeIndexing.ts
├── 缓存层: memoize.ts, fileReadCache.ts, completionCache.ts
├── 安全层: sandbox/, permissions/, subprocessEnv.ts
├── 错误处理层: errors.ts, toolErrors.ts
├── 哈希与加密: hash.ts, crypto.ts, fingerprint.ts
├── 进程管理: process.ts, cleanup.ts, cleanupRegistry.ts, lockfile.ts
└── 网络与HTTP: http.ts, proxy.ts, api.ts, apiPreconnect.ts

二、文件系统抽象层

2.1 fsOperations.ts — 文件系统接口 (771行)

这是整个系统的文件操作抽象层。定义了 FsOperations 接口类型,包含 40+ 个方法,涵盖所有文件系统操作:

export type FsOperations = {
cwd(): string
existsSync(path: string): boolean
stat(path: string): Promise<fs.Stats>
readdir(path: string): Promise<fs.Dirent[]>
statSync(path: string): fs.Stats
readFileSync(path: string, options: { encoding: BufferEncoding }): string
readFileBytesSync(path: string): Buffer
readSync(path: string, options: { length: number }): { buffer: Buffer, bytesRead: number }
mkdirSync(path: string, options?: { mode?: number }): void
writeFileSync(path: string, content: string, options?: ...): void
isDirEmptySync(path: string): boolean
// ... 以及 rename, unlink, symlink, link, copy, glob 等
}

关键设计: getFsImplementation() 返回当前的 FS 实现。生产环境返回真实的 node:fs 封装;测试环境可注入 mock。所有文件操作必须通过此接口,不允许直接调用 fs.*

safeResolvePath(fs, filePath) 是安全路径解析函数,解析符号链接并返回真实路径,防止路径穿越攻击。

2.2 file.ts — 文件操作工具集 (585行)

建立在 fsOperations.ts 之上的高级文件操作:

核心函数清单

函数 作用 关键细节
pathExists(path) 异步检查路径存在性 基于 stat() 的 try/catch
readFileSafe(filepath) 安全读取文件 出错返回 null 而非抛出
readFileSyncCached(filePath) 带缓存的文件读取 通过 fileReadCache 避免重复I/O
writeTextContent(path, content, encoding, endings) 写入文本 自动处理 CRLF/LF 转换
writeFileSyncAndFlush_DEPRECATED(path, content, opts) 原子写入 先写临时文件→rename
detectFileEncoding(filePath) 检测文件编码 通过 BOM 和字节分析
detectLineEndings(filePath, encoding) 检测行结尾符 读取前4096字节判断
addLineNumbers({content, startLine}) 添加行号前缀 支持紧凑和宽松两种格式
getDisplayPath(filePath) 路径美化显示 相对路径/波浪线/绝对路径优先级
findSimilarFile(filePath) 查找同名不同扩展名文件 用于模型写错扩展名时的纠错
suggestPathUnderCwd(requestedPath) 路径纠错建议 检测”丢失仓库目录”模式

原子写入机制 (writeFileSyncAndFlush_DEPRECATED)

这是最复杂的文件写入函数,源码逻辑:

1. 检测目标是否为符号链接
└── 如果是 → 读取链接目标路径,写入真实路径(保持链接完整性)

2. 获取目标文件权限 (mode)
└── 如果文件存在 → statSync 获取 mode
└── 如果不存在 → 使用 options.mode 或默认权限

3. 原子写入尝试
├── 创建临时文件: ${targetPath}.tmp.${process.pid}.${Date.now()}
├── writeFileSync(tempPath, content, { flush: true })
├── 如果文件已存在 → chmodSync(tempPath, targetMode)
└── renameSync(tempPath, targetPath) // POSIX 上这是原子操作

4. 失败回退
├── 清理临时文件
└── 直接写入目标路径(非原子但不丢数据)

为什么有 _DEPRECATED 后缀: 同步写入会阻塞事件循环,应优先使用 fs.promises.writeFile + flush 选项。但由于许多代码路径依赖同步语义(如 Edit 工具),暂时保留。

行号前缀系统

addLineNumbers() 支持两种格式,通过 GrowthBook 特性开关切换:

紧凑格式(默认):  "42\t这是第42行"          // 每行3-6字节
宽松格式(旧版): " 42→这是第42行" // 每行9字节

节省的开销: 1.35B ReadFile调用 × 132行均值 → 2.18% 的全系统非缓存输入token

stripLineNumberPrefix(line) 是反向操作,用正则 /^\s*\d+[→\t](.*)$/ 剥离行号前缀。

2.3 fileReadCache.ts — 文件读取缓存

readFileSyncCached() 使用这个缓存层。当 FileEditTool 在同一个 agentic loop turn 中多次读取同一文件时,避免重复 I/O 操作。缓存按文件路径索引,基于 mtime 判断失效。


三、Shell执行引擎

3.1 Shell.ts — Shell执行核心 (475行)

这是所有命令执行的入口点。

Shell发现 (findSuitableShell())

  1. 检查 CLAUDE_CODE_SHELL 环境变量(显式覆盖)
  2. 检查 SHELL 环境变量(用户偏好)
  3. 只支持 bashzsh(不支持 fish/sh/dash/csh 等)
  4. 搜索路径: /bin, /usr/bin, /usr/local/bin, /opt/homebrew/bin
  5. which() + isExecutable() 验证可执行性
  6. Nix 环境特殊处理: accessSync(X_OK) 可能失败时 fallback 到实际执行 --version

PowerShell: 作为独立 provider 通过 getPsProvider() 支持 Windows PowerShell 5.1 和 pwsh 7+。

命令执行 (exec())

exec(command: string, abortSignal: AbortSignal, shellType: ShellType, options?: ExecOptions)

完整执行流程:

1. 解析 ShellProvider → provider.buildExecCommand()
└── 构建完整的 shell 命令字符串(包含 cwd 跟踪的 pwd -P)

2. CWD 恢复检查
└── 如果当前 cwd 不存在(被命令删除了)→ 回退到 originalCwd

3. 沙箱封装(如果 shouldUseSandbox)
└── SandboxManager.wrapWithSandbox(command, shell)
└── 创建安全的临时目录 /tmp/.claude-<user>/

4. 进程启动
├── spawn(shell, args, { env: subprocessEnv(), cwd, detached, ... })
├── 环境变量: 继承 subprocessEnv() + GIT_EDITOR=true + CLAUDECODE=1
├── stdio: pipe模式 或 文件fd模式
│ ├── 文件fd: stdout/stderr 共享同一 fd,O_APPEND 保证原子写入
│ └── pipe模式: 实时回调 onStdout + StreamWrapper
└── 安全标志: O_NOFOLLOW 防止符号链接攻击

5. 超时管理
└── 默认 30分钟,SIGTERM → 5秒后 SIGKILL

6. 完成后处理
├── 读取 pwd 文件获取新 cwd
├── NFC 规范化 Unicode 路径(macOS APFS → NFD 问题)
├── 更新 cwd 状态 + 触发文件变更钩子
└── 清理 bwrap 留下的 0 字节挂载点文件

关键安全设计

  • GIT_EDITOR=true: 防止 git 打开编辑器(如 rebase -i)导致 hang
  • CLAUDECODE=1: 让脚本检测是否在 Claude Code 内运行
  • subprocessEnv(): 在 GitHub Actions 中清除敏感环境变量(API keys 等)
  • sandbox: macOS 上使用 sandbox-exec,Linux 上使用 bwrap (bubblewrap)
  • O_NOFOLLOW: 防止符号链接跟随攻击

3.2 ShellCommand.ts — 命令结果封装

wrapSpawn()child_process.spawn() 封装为统一的 ShellCommand 对象:

type ShellCommand = {
result: Promise<ExecResult> // 最终结果
interrupt(): void // 发送 SIGINT
terminate(): void // tree-kill 整个进程树
}

type ExecResult = {
stdout: string
stderr: string
code: number
interrupted: boolean
backgroundTaskId?: string // 后台任务标识
}

3.3 子进程环境隔离 (subprocessEnv.ts)

在 GitHub Actions 中(CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1),从子进程环境中清除:

  • Anthropic认证: ANTHROPIC_API_KEY, CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_AUTH_TOKEN
  • 云提供商凭证: AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, GOOGLE_APPLICATION_CREDENTIALS
  • GitHub Actions OIDC: ACTIONS_ID_TOKEN_REQUEST_TOKEN (泄露可导致仓库接管)
  • Actions缓存: ACTIONS_RUNTIME_TOKEN (泄露可导致缓存投毒→供应链攻击)

同时清除对应的 INPUT_* 变量(GitHub Actions 自动为 with: 输入创建的)。


四、Git操作系统

4.1 git.ts — Git核心操作 (927行)

Git根目录发现 (findGitRoot)

使用向上遍历算法查找 .git 目录或文件(worktree/子模块用文件):

从 startPath 开始
├── 检查 join(current, '.git') → statSync
│ ├── 是目录(常规仓库)→ 返回 current
│ └── 是文件(worktree/子模块)→ 返回 current
└── current = dirname(current) // 向上一级

缓存: 使用 memoizeWithLRU(最大50条),防止无界内存增长——gitDiffdirname(file) 调用此函数,编辑多文件时会累积。

规范化根目录解析 (findCanonicalGitRoot)

Worktree 场景中,多个工作树共享同一仓库。resolveCanonicalRoot() 通过以下链路找到主仓库:

.git (file) → "gitdir: path" → commondir file → 主仓库 .git 目录 → 父目录

安全验证(防止恶意仓库攻击):

  1. worktreeGitDir 必须是 <commonDir>/worktrees/ 的直接子目录
  2. <worktreeGitDir>/gitdir 必须指回 <gitRoot>/.git

如果验证失败,返回原始 gitRoot(不跟随恶意 commondir 指针)。

远程URL规范化 (normalizeGitRemoteUrl)

统一不同克隆方式的URL格式:

git@github.com:owner/repo.git    → github.com/owner/repo
https://github.com/owner/repo.git → github.com/owner/repo
ssh://git@github.com/owner/repo → github.com/owner/repo
http://proxy@127.0.0.1:PORT/git/owner/repo → github.com/owner/repo (CCR代理)

所有输出小写化,用于生成仓库唯一标识哈希。

其他Git操作

函数 用途
getIsGit() 检测当前目录是否在Git仓库中
getHead() 获取当前HEAD
getBranch() 获取当前分支名
getDefaultBranch() 获取默认分支(main/master)
getRemoteUrl() 获取远程仓库URL
getRepoRemoteHash() SHA256(规范化URL) 的前16字符
hasUnpushedCommits() 检测是否有未推送的提交

所有分支/HEAD信息通过 git/gitFilesystem.ts 直接读取 .git/ 目录文件获取(避免 spawn git 进程的开销)。


五、Token计算与上下文管理

5.1 tokens.ts — Token统计核心 (262行)

关键函数架构

tokenCountWithEstimation()     ← 规范的上下文大小计算入口
├── getTokenUsage() ← 从消息中提取 API usage
├── getTokenCountFromUsage() ← input + output + cache tokens
└── roughTokenCountEstimation() ← 新消息的粗略估算

tokenCountWithEstimation() — 上下文窗口大小计算

这是整个系统中最关键的Token函数,所有阈值判断(自动compact、会话记忆初始化)都使用它:

export function tokenCountWithEstimation(messages: readonly Message[]): number

算法:

  1. 从消息列表末尾向前搜索最后一条有 usage 的 assistant 消息
  2. 并行工具调用处理: 当模型做并行工具调用时,流式代码为每个 content block 生成独立的 assistant 记录(共享 message.id)。如果只从最后一条记录开始估算,会遗漏中间插入的 tool_result 消息
  3. 因此,找到有 usage 的记录后,向前回溯到第一个共享相同 message.id 的 sibling 记录
  4. 返回 getTokenCountFromUsage(usage) + roughTokenCountEstimation(messages.slice(anchor + 1))

getTokenCountFromUsage() — 完整上下文Token数

input_tokens + cache_creation_input_tokens + cache_read_input_tokens + output_tokens

finalContextTokensFromLastResponse() — 预算计算专用

用于 task_budget.remaining 计算。当服务端有 iterations(服务端工具循环)时,使用最后一轮迭代的 input_tokens + output_tokens;否则使用顶层的。

不包含 cache tokens(匹配服务端公式)。

5.2 context.ts — 上下文窗口管理 (222行)

常量定义

MODEL_CONTEXT_WINDOW_DEFAULT = 200_000      // 默认上下文窗口
COMPACT_MAX_OUTPUT_TOKENS = 20_000 // compact操作最大输出
MAX_OUTPUT_TOKENS_DEFAULT = 32_000 // 默认最大输出
MAX_OUTPUT_TOKENS_UPPER_LIMIT = 64_000 // 输出上限
CAPPED_DEFAULT_MAX_TOKENS = 8_000 // 槽位保留优化的封顶值
ESCALATED_MAX_TOKENS = 64_000 // 重试时升级到的最大值

槽位保留优化: BQ (BigQuery) 分析显示 p99 输出仅 4,911 tokens,但默认 32k/64k 过度保留了 8-16 倍容量。封顶到 8k 后,<1% 的请求命中限制,那些请求会自动以 64k 重试。

getContextWindowForModel() — 模型上下文窗口解析

优先级从高到低:

  1. CLAUDE_CODE_MAX_CONTEXT_TOKENS 环境变量(仅 ant)
  2. 模型名中的 [1m] 后缀 → 1,000,000
  3. 模型能力表 getModelCapability()max_input_tokens
  4. Beta header CONTEXT_1M_BETA_HEADER + 模型支持 1M
  5. Sonnet 1M 实验组
  6. ant 内部模型配置
  7. 默认 200,000

六、配置管理系统

6.1 config.ts — 配置核心 (1818行)

这是整个系统中最大的单一文件之一。

两级配置架构

GlobalConfig (全局)
├── 存储位置: ~/.claude/config.json
├── 内容: 用户偏好、认证、主题、编辑器模式、MCP服务器...
├── 150+ 字段(源码中详尽定义)
└── 跨项目共享

ProjectConfig (项目级)
├── 存储位置: GlobalConfig.projects[projectPath]
├── 内容: 允许的工具、MCP上下文URI、上次会话成本...
├── 按项目路径索引
└── 信任对话状态在此存储

GlobalConfig 关键字段分类

认证相关:

  • primaryApiKey: OAuth 管理的 API key
  • oauthAccount: 账户信息(UUID、邮箱、组织)
  • customApiKeyResponses: 用户批准/拒绝的第三方 API key

UI偏好:

  • theme: ‘dark’ | ‘light’ | ‘light-daltonized’ | ‘dark-daltonized’
  • editorMode: ‘normal’ | ‘vim’ | ‘emacs’
  • diffTool: ‘terminal’ | ‘auto’
  • todoFeatureEnabled, showExpandedTodos
  • fileCheckpointingEnabled, terminalProgressBarEnabled

模型与实验:

  • cachedStatsigGates: Statsig 门控值缓存
  • cachedGrowthBookFeatures: GrowthBook 特性值缓存
  • growthBookOverrides: 本地覆盖(仅 ant)
  • s1mAccessCache: Sonnet 1M 访问权限缓存

追踪计数器(大量用于控制UI提示的展示频率):

  • numStartups, memoryUsageCount, promptQueueUseCount
  • voiceNoticeSeenCount, subscriptionNoticeCount
  • 各种 *Dismissed 布尔值

配置读写机制

saveGlobalConfig(updater: (config) => config)
├── lockfile.lock(configPath) // 文件锁防并发写
├── fresh = readConfigFromDisk() // 重新读取(可能被其他进程修改)
├── wouldLoseAuthState(fresh)? // 防止写入损坏的配置覆盖认证
├── updated = updater(fresh) // 应用更新
├── writeFileSyncAndFlush(path, JSON) // 原子写入
└── lockfile.unlock() // 释放锁

getGlobalConfig()
├── 防止重入递归(insideGetConfig guard)
├── configFileModTime 变化检测
├── readFileSync + safeParseJSON
└── 深合并 defaultConfig + parsed

认证保护机制 (wouldLoseAuthState): 如果新读取的配置缺少 oauthAccounthasCompletedOnboarding(但内存缓存中有),说明磁盘上的配置被损坏了——拒绝写入,防止认证状态丢失。

信任对话系统 (checkHasTrustDialogAccepted)

1. 检查会话级信任(home目录运行时,信任不持久化)
2. 检查项目配置路径(Git root 或 originalCwd)
3. 从当前 cwd 向上遍历所有父目录
└── 任一父目录有信任记录 → 子目录自动被信任

七、认证系统

7.1 auth.ts — 认证核心 (2003行)

第二大文件,处理所有认证场景。

认证源优先级

1. ANTHROPIC_API_KEY 环境变量       → 直接 API Key
2. CLAUDE_CODE_OAUTH_TOKEN → 受管 OAuth (CCR/Desktop)
3. CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR → 通过 fd 传递的 OAuth token
4. apiKeyHelper(settings.json) → 外部密钥管理工具
5. claude.ai OAuth tokens → /login 获取的 OAuth 令牌
6. 无认证 → 报错

OAuth Token管理

getClaudeAIOAuthTokens()
├── 从安全存储读取 access_token + refresh_token
├── isOAuthTokenExpired()?
│ └── refreshOAuthToken() // 自动刷新
└── 返回 { accessToken, refreshToken, scopes }

API Key Helper

用户可以在 settings.json 中配置 apiKeyHelper 字段指向一个外部命令(如密钥管理器),Claude Code 执行该命令获取 API key:

getApiKeyFromApiKeyHelperCached()
├── execSyncWrapper(apiKeyHelper, { timeout: 30s })
├── 结果用 memoizeWithTTL(5min) 缓存
└── 安全: 只在信任对话通过后才执行

受管 OAuth 上下文

isManagedOAuthContext(): CCR (Cloud Container Runtime) 和 Claude Desktop 通过 OAuth token 启动 CLI,不应 fallback 到用户的 ~/.claude/settings.json 中的 API key 配置。


八、搜索引擎 — Ripgrep集成

8.1 ripgrep.ts (680行)

Ripgrep配置解析

三种模式(按优先级):

1. system模式:   用户通过 USE_BUILTIN_RIPGREP=false 选择系统 rg
2. embedded模式: bundled (native) 模式,rg 编译进 bun,通过 argv[0]='rg' 派发
3. builtin模式: vendor/ripgrep/ 下的预编译二进制

安全考虑: 使用系统 rg 时,用命令名 'rg' 而非完整路径,防止 PATH 劫持(当前目录下恶意的 ./rg.exe)。

执行模式

ripGrep() — 标准模式:缓冲全部 stdout,返回 string[]

  • 最大缓冲: 20MB(大型 monorepo 可有 200k+ 文件)
  • 超时: 20s (Linux/macOS), 60s (WSL,因文件读取性能差 3-5x)
  • EAGAIN 重试: 资源受限环境(Docker、CI)中自动降级为单线程 -j 1

ripGrepStream() — 流式模式:逐块回调 onLines,首批结果在 rg 还在遍历时就可绘制

  • 用于交互式搜索(fzf change:reload 模式)
  • 处理 \r\n 跨块边界

ripGrepFileCount() — 计数模式:流式计数换行符,峰值内存仅 ~64KB

  • 仅用于遥测(countFilesRoundedRg
  • 不缓冲 stdout

超时和终止策略

超时到达 → SIGTERM
└── 5秒后仍未退出 → SIGKILL (不可被捕获或忽略)

Windows 上 child.kill('SIGTERM') 会抛异常,使用默认信号。


九、缓存与记忆化

9.1 memoize.ts (270行)

提供三种记忆化策略:

memoizeWithTTL — 带TTL的写穿缓存

缓存未命中 → 同步计算,存入缓存
缓存新鲜 → 直接返回
缓存过期 → 立即返回旧值,后台异步刷新

关键: “过期返回旧值+后台刷新” 模式确保调用者不阻塞。身份守卫(identity-guard)防止并发 cache.clear() + 冷未命中 竞态。

memoizeWithTTLAsync — 异步版写穿缓存

新增 in-flight 去重:并发冷未命中共享同一 Promise(不会并发执行多次 aws sso login)。

并发调用者 A → 冷未命中 → 创建 Promise → inFlight.set(key, promise)
并发调用者 B → 冷未命中 → 发现 inFlight → 共享同一 Promise
Promise 完成 → cache.set() + inFlight.delete()

memoizeWithLRU — LRU缓存

基于 lru-cache 库。防止无界内存增长(之前用 lodash memoize 导致 300MB+)。

const cache = new LRUCache<string, Result>({ max: maxCacheSize })

cache.peek() 用于观察而不更新最近使用顺序。


十、沙箱安全系统

10.1 sandbox/sandbox-adapter.ts (986行)

建立在 @anthropic-ai/sandbox-runtime 之上的适配层。

沙箱原理

  • macOS: sandbox-exec (App Sandbox)
  • Linux: bwrap (bubblewrap) — 同一技术用于 Flatpak

路径模式解析

Claude Code 权限规则使用特殊路径前缀:

  • //path → 绝对路径(去掉一个 /,变成 /path
  • /path → 相对于 settings 文件目录
  • ~/path → 用户主目录(交给 sandbox-runtime 处理)
  • ./path → 当前工作目录相对路径

权限到沙箱规则的转换

从 Claude Code 的 settings.json 权限规则(如 Bash(npm install), Read(/src/**))转换为 sandbox-runtime 的 FsReadRestrictionConfig, FsWriteRestrictionConfig, NetworkRestrictionConfig


十一、Diff引擎

11.1 diff.ts (178行)

使用 diff npm 包的 structuredPatch() 生成差异。

特殊字符转义: &$ 会干扰 diff 库,写入前替换为标记(<<:AMPERSAND_TOKEN:>>),diff 后替换回来。

行数统计: countLinesChanged() 统计 patch 中 +- 开头的行数,更新到:

  1. addToTotalLinesChanged() → 成本追踪器
  2. getLocCounter().add() → OpenTelemetry
  3. logEvent('tengu_file_changed', ...) → 遥测

十二、哈希与加密

12.1 hash.ts

三个哈希函数,按场景选择:

函数 算法 速度 用途
djb2Hash(str) DJB2 极快 跨运行时稳定的目录名生成
hashContent(content) Bun.hash (wyhash) / sha256 ~100x vs sha256 文件变更检测
hashPair(a, b) seed-chain wyhash / sha256 双字符串哈希,无需拼接

hashPair 的精妙之处: Bun 路径用种子链(hash(a) 作为 hash(b) 的 seed),自然消歧 ("ts","code") vs ("tsc","ode"),无需分隔符。Node 路径用 \0 分隔。


十三、进程管理

13.1 lockfile.ts — 延迟加载的文件锁

let _lockfile: Lockfile | undefined

function getLockfile(): Lockfile {
if (!_lockfile) {
_lockfile = require('proper-lockfile') // 延迟加载,节省 ~8ms 启动时间
}
return _lockfile
}

proper-lockfile 依赖 graceful-fs(monkey-patch 所有 fs 方法),静态导入会拖慢启动。延迟到首次实际锁定时才加载。

13.2 cleanupRegistry.ts — 资源清理注册表

全局清理函数注册中心。进程退出时按注册顺序反向执行所有清理函数(临时文件删除、连接关闭等)。

13.3 sleep.ts — 可中止的休眠

sleep(ms, signal?, opts?)

支持 AbortSignal:退出时不必等待 backoff 休眠完成。

选项:

  • throwOnAbort: 中止时抛异常(让 retry 循环冒泡取消)
  • abortError: 自定义异常工厂
  • unref: 不阻止进程退出

十四、错误处理体系

14.1 errors.ts (239行)

错误类层次

Error
├── ClaudeError // 基础错误类
├── AbortError // 用户/系统中止
├── MalformedCommandError // 命令格式错误
├── ShellError // Shell执行失败
│ └── { stdout, stderr, code, interrupted }
├── ConfigParseError // 配置文件解析失败
│ └── { filePath, defaultConfig }
├── TeleportOperationError // teleport操作失败
└── TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
└── { telemetryMessage } // 可安全上报的错误

isAbortError() — 统一的中止检测

function isAbortError(e: unknown): boolean {
return (
e instanceof AbortError || // 自定义 AbortError
e instanceof APIUserAbortError || // SDK 的中止错误
(e instanceof Error && e.name === 'AbortError') // DOMException
)
}

为什么用 instanceof 检查 SDK 错误: 压缩构建后类名被 mangle(变成 nJT 之类),SDK 也不设置 this.name,所以字符串匹配在生产环境会静默失败。

TelemetrySafeError 的设计哲学

遥测消息不能包含文件路径、URL、代码片段等敏感信息。使用超长类名 TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS 强迫开发者在使用前确认消息内容安全。支持双消息模式:

throw new TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS(
`MCP tool timed out after ${ms}ms`, // 完整消息给用户
'MCP tool timed out' // 脱敏消息给遥测
)

十五、设计哲学总结

  1. 抽象层隔离: fsOperations 接口使文件系统可 mock,Shell 封装使 shell 差异透明化
  2. 安全纵深防御: 沙箱 + 环境变量清除 + O_NOFOLLOW + 路径验证 + 信任对话,多层保护
  3. 启动速度优先: 延迟加载(lockfile、ripgrep)、memoize、LRU 缓存,每毫秒都精打细算
  4. 内存控制: 从 lodash memoize 无界缓存到 LRU + TTL,解决了 300MB+ 的内存膨胀
  5. 并发安全: 文件锁、in-flight 去重、identity-guard 防竞态
  6. 跨平台兼容: Windows (WSL)、macOS、Linux 三平台的 shell/路径/权限差异都在 utils 层处理
  7. 遥测内建: 几乎每个子系统都有 logEvent() 和 OpenTelemetry 计数器
  8. 可观测性: logForDebugging() 遍布各处,diagLogs 用于无PII的诊断日志
打赏
  • 微信
  • 支付宝

评论