Skip to content
源码分析手册

错误分类与安全上报:在用户体验与隐私边界间寻找平衡

对应官方文档:claude-code-docs/docs/08-analytics-telemetry/error-classification.md(英文版)。

核心概念

2.1.88 源码中,错误处理远超简单的 try-catch 和控制台打印。它是一套面向三方受众的“分类分级系统”。

这套系统需要同时满足:

  1. 告知模型(Model feedback):给 Claude 提供足够的上下文(如错误栈、原因),让它决定是重试、修复还是放弃。
  2. 告知用户(User messaging):提供清晰、带路径或具体原因的报错,方便人工介入。
  3. 告知遥测(Telemetry reporting):将错误归类上报(如 Auth、Network、FS),但必须剔除 PII(个人身份信息),确保遥测后端看不到敏感的文件路径或代码片段。

源码级拆解

这套机制的核心位于 src/utils/errors.ts 和各业务入口的分类器中:

  1. 分类分级基础(Error Taxonomy)
    • ClaudeError:所有 CLI 内部报错的基类,确保统一的命名规范(name 字段),便于在 Catch 块中进行模式匹配。
    • TelemetrySafeError:这是隐私保护的核心。它分离了用户消息(可以包含文件路径)和遥测消息(保证是安全的、匿名的)。如果你在源码里看到 TelemetrySafeError_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,这说明作者要求开发者显式确认上报内容不含 PII。
  2. 异步与网络错误归一化(Network Classification)src/utils/errors.ts 中的 classifyAxiosError 负责将各种 HTTP 报错打上标签。它将错误归类为:auth(401/403)、timeout(超时)、network(DNS/拒绝连接)和 http(其他)。这让云端统计能精准区分是网络波动还是授权过期。
  3. 模型友好的错误剪枝(Stack Pruning):当错误被抛回给模型时,系统会调用 shortErrorStack(...) 将堆栈修剪为前 5 帧。这避免了将成千上万个 Token 浪费在冗长的库内部调用链上,只保留最相关的业务代码帧。
  4. 文件系统感知(FS Sensitivity)isFsInaccessible 用于识别特定的 FS 错误(如 ENOENTEACCES)。这让模型能智能地判断:是目录不存在(需要创建),还是权限不足(需要提示用户手动修改)。
  5. 中止状态管理(Abort Handling)AbortError 同时捕获 AbortController 信号和 Anthropic SDK 的各种中止语义。这确保了用户 Ctrl-C 取消操作时,系统能优雅地清理资源并停止上报事件,而不是将其记为“非预期崩溃”。
  6. 权限决策分类(Permission Decision Classification):在 toolExecution.ts 和权限 Prompt 逻辑中,决策结果会被打上 decisionClassification 标签(如 allow_alwaysdeny_once)。这些标签是预设的,不含具体文件名,从而在不侵犯隐私的情况下收集用户的授权偏好趋势。

踩坑指南

  1. PII 兜底清洗:即使没有使用 TelemetrySafeError,上报到 Datadog 的错误消息也会经过一层正则清洗,剔除潜在的路径和代码。
  2. 类型转换安全(toError):由于三方库可能抛出字符串、Null 或非 Error 对象,源码中大量使用了 toError() 封装,确保每个 Catch 块拿到的都是有标准的 Error 实例。
  3. 截断规则:所有上报的消息都有 20KB 左右的硬上限,防止超长报错消耗过多的网络带宽或后端存储。
  4. Expiry 自动检测:通过 isExpiredErrorType,系统能识别过期的 Session 并将其归类为“信息级(Info)”而非“错误级(Error)”,从而降低生产环境的告警噪音。

延伸阅读

  • 如果你关心这些错误如何最终流向后端,下一篇该看 Analytics Pipeline
  • 如果你关心如何手动调试这些错误,去看 verbose 模式下的 logForDebugging 实现
  • 如果你关心模型如何尝试自主修复错误,去看 Self-Correction 模式

源码锚点

  • claude-code-opensource/src/utils/errors.ts:整个错误分类系统的定义:ClaudeErrorTelemetrySafeErrorclassifyAxiosErrortoError
📄 src/utils/errors.ts — 整个错误分类系统的定义:`ClaudeError`、`TelemetrySafeError`、`classifyAxiosError`、`toError`。L3-8 of 239
typescript
export class ClaudeError extends Error {
  constructor(message: string) {
    super(message)
    this.name = this.constructor.name
  }
}
  • claude-code-opensource/src/services/analytics/index.ts:错误事件的上报入口,以及隐私清洗逻辑。
📄 src/services/analytics/index.ts — 错误事件的上报入口,以及隐私清洗逻辑。L19-48 of 174
typescript
export type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS = never

/**
 * Marker type for values routed to PII-tagged proto columns via `_PROTO_*`
 * payload keys. The destination BQ column has privileged access controls,
 * so unredacted values are acceptable — unlike general-access backends.
 *
 * sink.ts strips `_PROTO_*` keys before Datadog fanout; only the 1P
 * exporter (firstPartyEventLoggingExporter) sees them and hoists them to the
 * top-level proto field. A single stripProtoFields call guards all non-1P
 * sinks — no per-sink filtering to forget.
 *
 * Usage: `rawName as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED`
 */
export type AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED = never

/**
 * Strip `_PROTO_*` keys from a payload destined for general-access storage.
 * Used by:
 *   - sink.ts: before Datadog fanout (never sees PII-tagged values)
 *   - firstPartyEventLoggingExporter: defensive strip of additional_metadata
 *     after hoisting known _PROTO_* keys to proto fields — prevents a future
 *     unrecognized _PROTO_foo from silently landing in the BQ JSON blob.
 *
 * Returns the input unchanged (same reference) when no _PROTO_ keys present.
 */
export function stripProtoFields<V>(
  metadata: Record<string, V>,
): Record<string, V> {
  let result: Record<string, V> | undefined
  • claude-code-opensource/src/services/tools/toolExecution.ts:如何将修剪后的错误栈(Short Stack)反馈给模型。
📄 src/services/tools/toolExecution.ts — 如何将修剪后的错误栈(Short Stack)反馈给模型。L8-11 of 1746
typescript
  type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
  logEvent,
} from 'src/services/analytics/index.js'
import {
  • claude-code-opensource/src/bridge/bridgeApi.tsisExpiredErrorType 探测与过期 Session 的分类处理。
📄 src/bridge/bridgeApi.ts — `isExpiredErrorType` 探测与过期 Session 的分类处理。L503-508 of 540
typescript
export function isExpiredErrorType(errorType: string | undefined): boolean {
  if (!errorType) {
    return false
  }
  return errorType.includes('expired') || errorType.includes('lifetime')
}
  • claude-code-opensource/src/utils/permissions/PermissionPromptToolResultSchema.ts:权限决策的分类标准(decisionClassification)。
📄 src/utils/permissions/PermissionPromptToolResultSchema.ts — 权限决策的分类标准(`decisionClassification`)。L61-66 of 128
typescript
    decisionClassification: decisionClassificationField(),
  }),
)

const PermissionDenyResultSchema = lazySchema(() =>
  z.object({
  • claude-code-opensource/src/utils/debug.ts:用于本地诊断的 logForDebugging 实现。
📄 src/utils/debug.ts — 用于本地诊断的 `logForDebugging` 实现。L203-207 of 269
typescript
export function logForDebugging(
  message: string,
  { level }: { level: DebugLogLevel } = {
    level: 'debug',
  },

基于 Claude Code v2.1.88 开源快照的深度分析