阶段 3: 业务工作流分析
场景选择
Claude Code CLI 的业务流程围绕两个核心场景展开:
| 编号 | 场景 | 说明 |
|---|---|---|
| 1 | 启动与初始化流程 (Initialization) | 从用户敲下 claude 到 REPL 就绪,涵盖配置加载、密钥验证、UI 渲染初始化 |
| 2 | 用户交互对话循环 (Conversation Loop with Tool Use) | 用户输入 → API 调用 → 流式解析 → 工具执行 → 结果反馈的完整闭环,是系统的核心循环 |
选择这两个场景的原因:场景 1 决定了系统的启动性能和配置正确性;场景 2 是用户 99% 时间所处的主循环,其中工具调用与权限检查构成了 Claude Code 区别于普通聊天 CLI 的核心差异点。
场景 1: 启动与初始化流程
流程概述
启动流程经历了从 Shell → Node.js 入口 → 初始化 → 主程序 → React/Ink 渲染实例的逐级加载过程。设计上刻意将快速路径(如 --version)与完整初始化路径分离,实现零延迟响应。
详细时序图
配置加载优先级(6 层)
启动期间 enableConfigs() 按以下优先级合并配置(优先级从高到低):
1. 环境变量覆盖 (CLAUDE_CODE_*)
2. 命令行参数 (--model, --permission-mode ...)
3. 项目级配置 (.claude/settings.json, .claude/settings.local.json)
4. 用户级配置 (~/.claude/settings.json)
5. 企业级 MDM 策略 (macOS: com.anthropic.claude-code, Windows: Registry)
6. 远程托管配置 (Remote Managed Settings)启动性能优化要点
| 优化策略 | 实现方式 | 效果 |
|---|---|---|
| 零加载快速路径 | --version 直接输出,不导入任何模块 | ~0ms 响应 |
| 并行副作用 | MDM 读取、Keychain 预取在 import 语句间并行启动 | 节省 ~65ms (macOS) |
| 延迟加载 | OpenTelemetry (~400KB)、gRPC (~700KB) 延迟到实际需要时导入 | 减少初始内存占用 |
| Memoized init | init() 使用 lodash memoize,保证仅执行一次 | 避免重复初始化 |
| profileCheckpoint | 全链路性能打点,可通过 --profile 查看耗时 | 可观测性 |
场景 2: 用户交互对话循环(核心循环)
流程概述
对话循环是 Claude Code 的核心所在。用户的每一次输入都会经历消息规范化 → 系统提示词构建 → API 流式调用 → 流式响应解析 → 工具调用/权限检查/执行 → 结果反馈的完整循环。当 Claude 的响应中包含工具调用时,工具执行结果会被反馈给 API 触发下一轮循环,直到 Claude 返回纯文本(无工具调用)的最终响应。
详细时序图
工具执行并发模型
StreamingToolExecutor 实现了一个精细的并发控制策略:
┌──────────────────────────────────────────────────────┐
│ StreamingToolExecutor │
│ │
│ 工具到达 ──┬── 并发安全? ──是──→ 立即并行执行 │
│ │ │
│ └── 否 ──→ 等待所有并行任务完成 │
│ → 独占执行 │
│ → 完成后恢复并行 │
│ │
│ 结果缓冲: 按工具接收顺序发出(非完成顺序) │
│ │
│ 错误处理: Bash 工具错误 → siblingAbortController │
│ → 立即终止兄弟进程 │
│ │
│ 流式回退: discard() → 丢弃失败尝试的全部结果 │
└──────────────────────────────────────────────────────┘消息规范化管线
normalizeMessagesForAPI() 是确保 API 调用成功的关键防御层。以下是处理管线中各步骤的作用:
| 步骤 | 函数 | 目的 |
|---|---|---|
| 1 | getMessagesAfterCompactBoundary() | 从最近的压缩边界开始,丢弃更早的历史 |
| 2 | W68() (filterWhitespaceOnlyAssistant) | 过滤纯空白的 assistant 消息 |
| 3 | $$Y() (fixEmptyAssistantContent) | 修补空内容的 assistant 消息(注入占位文本) |
| 4 | D68() (filterOrphanedThinking) | 移除没有对应正文的孤立 thinking block |
| 5 | z$Y() (filterTrailingThinking) | 移除尾部多余的 thinking/redacted_thinking block |
| 6 | KZK() (ensureToolResultPairing) | 核心 - 修补 tool_use / tool_result 配对缺失 |
| 7 | _ZK() (stripAdvisorBlocks) | 移除 advisor 相关的内部 block |
| 8 | hqK() (stripThinkingForNonThinkingModels) | 对不支持 thinking 的模型移除 thinking block |
权限模式对比
权限级别递增 →
┌─────────┬──────────┬───────────┬──────────┬──────────────────┐
│ default │ plan │ autoEdit │ fullAuto │ bypassPermissions│
├─────────┼──────────┼───────────┼──────────┼──────────────────┤
│ 文件读取 │ 自动 ✓ │ 自动 ✓ │ 自动 ✓ │ 自动 ✓ │
│ Grep │ 自动 ✓ │ 自动 ✓ │ 自动 ✓ │ 自动 ✓ │
│ Glob │ 自动 ✓ │ 自动 ✓ │ 自动 ✓ │ 自动 ✓ │
│ 文件写入 │ 询问用户 │ 询问用户 │ 自动 ✓ │ 自动 ✓ │
│ 文件编辑 │ 询问用户 │ 询问用户 │ 自动 ✓ │ 自动 ✓ │
│ Bash │ 询问用户 │ 询问用户 │ 询问用户 │ 自动 ✓ │
│ 危险操作 │ 询问用户 │ 询问用户 │ 询问用户 │ 自动 ✓ │
└─────────┴──────────┴───────────┴──────────┴──────────────────┘上下文压缩子系统
当对话累积的 token 数量接近模型的上下文窗口限制时,压缩系统自动介入,防止 prompt_too_long 错误。
压缩时序图
压缩策略对比
| 维度 | microCompact | autoCompact | sessionMemoryCompact |
|---|---|---|---|
| 触发时机 | 每轮对话后 | token 超过阈值 | autoCompact 的优先路径 |
| 压缩目标 | 单个旧工具结果 | 整段对话历史 | 基于提取的会话记忆 |
| API 调用 | 无 (本地操作) | 需要 (生成摘要) | 无 (使用已有记忆) |
| 压缩粒度 | 工具级 | 对话级 | 对话级 |
| 信息损失 | 中 (工具输出) | 高 (全部旧消息) | 低 (保留关键记忆) |
| 性能开销 | 极低 | 较高 (~1次API) | 低 |
| 阈值 | 基于时间/token | contextWindow - 13K | 优先于标准压缩 |
| 连续失败保护 | 无 | MAX=3次后停止重试 | 不足时回退 |
关键数据流总结
用户输入
│
▼
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 消息规范化 │ ──→ │ 系统提示词构建 │ ──→ │ API 请求组装 │
│ │ │ │ │ │
│ • normalize │ │ • CLI prefix │ │ • model │
│ • ensurePairing │ │ • CLAUDE.md │ │ • system[] │
│ • stripBlocks │ │ • Git context │ │ • messages[] │
│ • media limit │ │ • tools prompt │ │ • tools[] │
└─────────────────┘ └──────────────────┘ │ • stream: true │
└────────┬─────────┘
│
▼
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ 上下文压缩 │ ←── │ 工具执行反馈 │ ←── │ 流式响应解析 │
│ │ │ │ │ │
│ • microCompact │ │ • permission │ │ • SSE parsing │
│ • autoCompact │ │ • hooks │ │ • text render │
│ • sessionMemory │ │ • tool.execute() │ │ • tool_use detect│
│ │ │ • tool_result │ │ • usage tracking │
└────────┬────────┘ └──────────────────┘ └──────────────────┘
│
│ 如果 stop_reason === "tool_use"
└──────────────→ 回到消息规范化 (循环)
如果 stop_reason === "end_turn"
└──────────────→ 展示最终响应给用户错误处理与恢复
对话循环中的错误处理覆盖了多个层次:
| 错误类型 | 处理策略 | 源码位置 |
|---|---|---|
| API 速率限制 (429) | 指数退避重试,显示倒计时 | services/api/withRetry.ts |
| 上下文过长 (prompt_too_long) | 触发自动压缩后重试 | query.ts |
| 工具执行失败 | 生成 is_error: true 的 tool_result,让模型自行调整 | StreamingToolExecutor.ts |
| 流式连接中断 | FallbackTriggeredError → 重试 | services/api/withRetry.ts |
| 用户中断 (Ctrl+C) | AbortController 信号传播,终止当前请求 | query.ts |
| 钩子执行错误 | 记录错误但不阻止主流程(除非 preventContinuation) | utils/hooks.ts |
| 连续压缩失败 | 3 次后停止重试,防止无限循环 | autoCompact.ts |
| tool_use/tool_result 不配对 | 合成错误占位符修补,记录诊断日志 | messages.ts |