难度:中等 | 时长:25 分钟 | 收获:掌握 7 个源码级省钱技巧,API 账单立减 30-60%
目标读者画像
正在使用或打算深度使用 Claude Code 的全栈工程师,尤其关注 API 成本控制的个人开发者和小型团队。如果你对 Claude Code 的认知还停留在「装上就用」阶段,这篇文章会告诉你它背后藏了多少可调参数。
核心依赖与环境
- Node.js 18+
- Claude Code 2.1.88
- TypeScript 基础(能读懂类型定义即可)
- npm 或 pnpm
- 一个有效的 ANTHROPIC_API_KEY
TIP
文章所有源码案例基于 2.1.88 版本,版本号查看方式:claude --version。不同版本源码细节可能略有差异,但核心机制一致。
完整项目结构树
# Claude Code 源码结构(相关目录)
src/
├── cost-tracker.ts # 成本追踪核心
├── costHook.ts # 成本输出 Hook
├── Tool.ts # 工具抽象工厂
├── tools.ts # 工具注册与过滤
├── tools/
│ ├── BashTool/ # Shell 执行工具
│ │ └── BashTool.tsx
│ │ └── readOnlyValidation.ts # 只读检测逻辑
│ ├── FileReadTool/ # 文件读取
│ ├── FileEditTool/ # 文件编辑
│ └── ...
├── services/
│ ├── compact/ # 上下文压缩服务
│ │ └── compact.ts
│ └── tools/
│ └── toolOrchestration.ts # 工具并发分区
├── skills/
│ └── loadSkillsDir.ts # 技能加载
├── types/
│ └── permissions.ts # 权限模式定义
├── upstreamproxy/ # 上游代理(企业级)
│ ├── upstreamproxy.ts
│ └── relay.ts
└── utils/
└── git.ts # Git / Worktree 工具
一、Claude Code 账单是怎么变贵的
在说省钱之前,我们先搞清楚钱是怎么花掉的。Claude Code 的 API 账单主要由三件事驱动:
1. 上下文 Token 总量
每次对话,Claude Code 都会把历史消息、工具调用结果、文件内容打包发给 API。上下文越大,每次请求的单价越高。在 cost-tracker.ts 里,每个 session 的消耗被精确追踪:
// src/cost-tracker.ts
interface ModelUsage {
inputTokens: number;
outputTokens: number;
cacheReadInputTokens: number; // 缓存命中(更便宜)
cacheCreationInputTokens: number; // 缓存创建(一次性费用)
webSearchRequests: number;
costUSD: number;
}
这里有两个关键点:cacheReadInputTokens 比普通 input token 便宜约 10 倍,而 cacheCreationInputTokens 是创建缓存时的一次性开销——这直接决定了我们后文的压缩策略。
2. 工具调用频率
Claude Code 平均每次对话会触发数十次工具调用。每次工具调用都会产生一次 API 请求(流式 tool use),工具越多、调用越频繁,上下文膨胀越快。
3. Agent 并发数
当你用 AgentTool 启动子 Agent,或者 Coordinator 模式同时调度多个 Worker,每次都是独立会话——成本直接翻倍。
Claude Code 内置了 cost-tracker.ts 来实时追踪这些消耗。退出时它会把数据持久化到项目配置里:
// src/costHook.ts
// 进程退出时自动保存并打印账单摘要
process.on('exit', () => {
if (hasConsoleBillingAccess()) {
console.log(formatTotalCost()); // 输出带 per-model 分解的账单
}
saveCurrentSessionCosts(getFpsMetrics?.());
});
WARNING
账单追踪是项目级别隔离的——不同目录的 session 消耗不会合并。如果你在多个项目里用 Claude Code,每个项目都有独立的成本记录。
二、省钱技巧 1:Compact 上下文压缩,节省 30-50%
Claude Code 内置了一个 compact 服务,它会把历史对话「压缩」成摘要,让上下文保持在一个合理规模。源码里的关键参数一目了然:
// src/services/compact/compact.ts
const POST_COMPACT_TOKEN_BUDGET = 50_000; // 压缩后总 Token 上限
const POST_COMPACT_MAX_TOKENS_PER_FILE = 5_000; // 单文件最多保留 5K Token
const POST_COMPACT_MAX_TOKENS_PER_SKILL = 5_000; // 单个 Skill 最多 5K Token
const POST_COMPACT_SKILLS_TOKEN_BUDGET = 25_000; // 所有 Skill 加起来 25K 上限
const POST_COMPACT_MAX_FILES_TO_RESTORE = 5; // 压缩后最多还原 5 个文件
压缩流程分三步:触发 → API 摘要 → 重新附加关键文件。它会先触发 pre_compact hooks,调用压缩 API(这是单独一次 API 请求),再触发 post_compact hooks,最后把最近读过的文件重新 attach 回来。
实战技巧: 调整触发阈值可以控制压缩频率,进而控制成本。
// settings.json — 在项目根目录的 .claude/ 下
{
"compact": {
"autoCompactThreshold": 0.85 // 上下文用到 85% 就触发压缩,默认值
}
}
降低阈值(比如 0.7)会更频繁地压缩,每次压缩成本更小但摘要质量可能略有下降;提高阈值(0.95)则相反。
TIP
如果你发现 Claude Code 在长对话后期「失忆」,很可能是压缩后关键上下文被截掉了。这时候可以手动在对话里补充关键信息,或者调低 autoCompactThreshold 让压缩更频繁、每次压缩量更小。
三、省钱技巧 2:善用 Read-only 工具识别,避免无谓消耗
Claude Code 的每个工具都有 isReadOnly() 方法——它决定了工具是否会产生副作用。工具调度器会根据这个标志决定是否允许并发执行:
// src/Tool.ts — 工具默认是保守的(fail-closed)
const TOOL_DEFAULTS = {
isConcurrencySafe: (_input) => false, // 默认不安全,不并发
isReadOnly: (_input) => false, // 默认有副作用
isDestructive: (_input) => false, // 默认非破坏性
};
关键在于 BashTool。它内置了详细的命令分类逻辑:
// src/tools/BashTool/BashTool.tsx
// 只读命令白名单 — 这些命令可以安全并发,不会修改任何状态
const BASH_SEARCH_COMMANDS = ['find', 'grep', 'rg', 'ag', 'ack', 'locate', 'which', 'whereis'];
const BASH_READ_COMMANDS = ['cat', 'head', 'tail', 'less', 'more', 'wc', 'stat', 'file', 'strings', 'jq', 'awk', 'cut', 'sort', 'uniq', 'tr'];
const BASH_LIST_COMMANDS = ['ls', 'tree', 'du'];
const BASH_SEMANTIC_NEUTRAL_COMMANDS = ['echo', 'printf', 'true', 'false', ':'];
实战技巧: 当你发出一个 ls -la 查询目录结构时,Claude Code 知道这是只读操作,会把它和其他只读操作打包并发执行,减少等待时间。但如果你执行 rm -rf node_modules,它会被识别为破坏性操作,必须串行处理,每次都会让你确认。
理解这套逻辑,你可以主动组织自己的命令来触发更好的并发优化:
# 好的实践:只读操作可以一条命令里组合,减少工具调用次数
find . -name "*.ts" | head -20 && wc -l src/**/*.ts
# 不好的实践:把破坏性操作混在只读命令里
find . -name "*.log" && rm -rf logs/ # 这条会被串行拦截
四、省钱技巧 3:Skills 系统减少重复 Prompt,节省 Prompt Token
Claude Code 支持 Skills 技能系统,本质上是一组可复用的 prompt 片段。当你在项目根目录建一个 skills/ 文件夹,里面放 Markdown 文件,Claude Code 就会自动加载它们:
your-project/
└── skills/
└── my-workflow/
└── SKILL.md # 目录格式:skill-name/SKILL.md
Skill 文件有 frontmatter 元数据:
---
name: my-skill
description: 重构 React 组件的标准流程
whenToUse: 当需要重构组件或拆分大型组件时使用
paths: ["src/**/*.tsx", "src/**/*.ts"] # 只在匹配这些路径时激活
allowedTools: [Read, Edit, Bash] # 限制可用工具
arguments: [filePath, scope]
executionContext: inline # inline 或 fork
---
# Skill 正文 — 这里写你的标准重构流程 prompt
为什么能省钱? 每当你新建一个对话,Claude Code 都需要在 system prompt 里塞入所有工具描述、规则和上下文。Skills 让常见的工作流标准化,减少你每次重复输入相同指令的 Token 消耗——省的是你手动复制粘贴的那部分 prompt 长度。
paths 字段是条件激活机制的灵魂——只有当匹配到指定路径时,这个 Skill 才会出现在候选列表里。这避免了无关 Skill 污染上下文:
// src/skills/loadSkillsDir.ts
// 条件激活使用 gitignore 风格匹配
activateConditionalSkillsForPaths(filePaths, cwd) {
// 当访问了 src/**/*.tsx 文件后,React 相关的 Skills 才激活
}
TIP
复用率高的 Skill 应该放在 ~/.claude/skills/(用户级别),项目级别的 Skill 只放项目特有的流程。层级越高的 Skill 影响范围越大,节省效果越明显。
五、省钱技巧 4:并发分区减少重试,节省 Rate Limit 消耗
Claude Code 不是把所有工具调用串行执行的。它有一个智能分区器,会把「可以同时跑」的工具放一组,把「必须排队」的工具单独处理:
// src/services/tools/toolOrchestration.ts
// 读取并发上限,默认为 10
function getMaxToolUseConcurrency() {
return parseInt(process.env.CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY ?? '10', 10);
}
// 工具分区:连续的是只读工具则合并,否则开新批次
function partitionToolCalls(toolUseMessages, toolUseContext): Batch[] {
return toolUseMessages.reduce((acc, toolUse) => {
const isConcurrencySafe = tool.isConcurrencySafe(parsedInput.data);
if (isConcurrencySafe && acc[acc.length-1]?.isConcurrencySafe) {
acc[acc.length-1].blocks.push(toolUse); // 并发安全,合并
} else {
acc.push({ isConcurrencySafe, blocks: [toolUse] }); // 新批次
}
return acc;
}, []);
}
实战技巧: 如果你的 MCP 工具支持并发,务必在定义时显式声明 isConcurrencySafe: true。没有声明的工具默认串行,而串行工具碰上 Rate Limit 后重试的 Token 浪费是隐性的,但累积起来很可观。
// settings.json — 为你的 MCP 工具声明并发安全
{
"mcpServers": {
"my-mcp": {
"command": "npx",
"args": ["-y", "my-mcp-tool"],
"isConcurrencySafe": true // 声明后可以和其他只读工具并发执行
}
}
}
并发上限也可以通过环境变量调高:
# 适合机器性能好、API 额度充足时使用
export CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY=20
六、省钱技巧 5:Worktree 隔离替代全量克隆,精简协作上下文
Claude Code 内置了 Worktree 支持。Git Worktree 让你在同一个仓库里检出多个分支到不同目录,不需要 clone 多份仓库。Claude Code 的 findCanonicalGitRoot 会自动解析 Worktree 路径:
// src/utils/git.ts
// 安全解析 Worktree 根目录
function resolveCanonicalRoot(gitRoot: string): string {
const gitDirContent = readFileSync(path.join(gitRoot, '.git'), 'utf8');
// .git 文件格式: gitdir: /path/to/.git/worktrees/<name>
const commonDir = extractCommonDir(gitDirContent);
// 安全校验:worktreeGitDir 必须是 commonDir/worktrees/ 的子目录
// 防止恶意仓库通过 .git 文件做 sandbox escape
validateWorktreeSecurity(worktreeGitDir, commonDir);
return mainRepoRoot;
}
省钱原理: 如果你的团队习惯每个功能分支 clone 一份仓库,那么每份 clone 的 .git 目录(可能几 GB)都会被 Claude Code 纳入上下文扫描范围。Worktree 模式下只有一个 .git 目录,上下文扫描体积大幅缩小。
# 创建 Worktree(而不是 clone 新份)
git worktree add ../feature-sidebar feature/sidebar
# Claude Code 会自动识别 Worktree,共享主仓库的上下文
# 切换到另一个 worktree 时,直接 cd 进去启动 claude
cd ../feature-sidebar && claude
WARNING
Worktree 安全机制很重要:Claude Code 会校验 .git 文件的路径不会被用来做 sandbox escape。但如果你在不可信仓库上使用 Worktree,仍然建议小心检查。
七、省钱技巧 6:Permission 模式调优,减少确认交互
Claude Code 有完整的权限系统,5 种模式决定了工具何时需要你确认:
// src/types/permissions.ts
type PermissionMode =
| 'acceptEdits' // 自动接受编辑
| 'bypassPermissions' // 完全绕过权限检查(危险但最快)
| 'default' // 按规则询问
| 'dontAsk' // 完全不询问
| 'plan' // Plan 模式
| 'auto'; // AI 自动分类决定(TRANSCRIPT_CLASSIFIER 开启时)
| 模式 | 行为 | 速度 | 安全性 |
|---|---|---|---|
default | 按规则询问 | 慢 | 安全 |
auto | AI 分类器自动决定 | 中 | 较安全 |
dontAsk | 不询问,直接执行 | 快 | 风险自担 |
bypassPermissions | 完全跳过检查 | 最快 | 高风险 |
plan | Plan 模式下不执行 | 最慢 | 最安全 |
实战技巧: 对于你完全信任的目录(你自己的项目,代码已经在 git 里),可以在 settings.json 里精细化配置:
// ~/.claude/settings.json(用户级)或 .claude/settings.json(项目级)
{
"permissions": {
"defaultMode": "auto",
"allow": [
{ "toolName": "Bash", "ruleContent": "pnpm*" },
{ "toolName": "Read" },
{ "toolName": "Glob" },
{ "toolName": "Grep" }
],
"deny": [
{ "toolName": "Bash", "ruleContent": "rm -rf /" }
],
"ask": [
{ "toolName": "Bash", "ruleContent": "git push" }
]
}
}
auto 模式是省力和安全的折中——它用 AI 分类器判断操作是否安全,避免不必要的弹窗。每次少一次确认交互,就少一次上下文切换的 Token 消耗。
WARNING
bypassPermissions 模式相当于把 Claude Code 变成 root 权限的脚本——任何输入都会被直接执行。仅限隔离的测试目录使用,绝对不要在主项目上开这个。
八、省钱技巧 7:企业级上游代理缓存,减少重复 API 调用
Claude Code 的 upstreamproxy 模块为 Claude Code Connect(CCR,企业版)提供代理路由。源码里的 NO_PROXY_LIST 值得关注:
// src/upstreamproxy/upstreamproxy.ts
const NO_PROXY_LIST = [
'localhost', '127.0.0.1', '::1',
'169.254.0.0/16', '10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16',
'anthropic.com', 'github.com', '*.github.com',
// npm / PyPI / Rust registry 全在白名单里,不需要走代理
'registry.npmjs.org', 'pypi.org', 'files.pythonhosted.org',
'index.crates.io', 'proxy.golang.org'
];
实战技巧: 即使你不是企业版,理解这套逻辑也能帮你优化本地配置。如果你在公司网络里,Claude Code 会自动把流量路由到企业代理,在代理层面可以做 Token 缓存和复用——这是CCR 企业用户省钱的底层机制。
对于自建 MCP 服务,可以在 MCP 配置里开启响应缓存:
// settings.json
{
"mcpServers": {
"local-knowledge": {
"command": "node",
"args": ["server.js"],
"env": {
"MCP_CACHE_TTL": "3600" // 缓存 1 小时,减少重复 API 调用
}
}
}
}
九、常见问题排查
Q1: 明明只改了 1 行,账单还是很高?
Claude Code 账单高不一定来自编辑操作,而可能来自「上下文膨胀」。长对话中即使只改了 1 行,压缩前的历史消息、工具结果都还在上下文里。检查方法:会话结束时看 formatTotalCost() 输出的 inputTokens,对比 outputTokens,前者越大说明上下文越臃肿。
Q2: Compact 压缩后回答质量明显下降?
这是压缩粒度问题。将 autoCompactThreshold 从默认值 0.85 调到 0.95,减少压缩频率,同时确保重要的设计决策和上下文信息在对话早期被明确记录,这样压缩后的摘要质量更高。
Q3: bypass 模式到底安不安全?
不安全,但在隔离环境里可控。如果你必须用 bypass,建议配合 permissions.deny 规则兜底:
{
"permissions": {
"defaultMode": "bypassPermissions",
"deny": [
{ "toolName": "Bash", "ruleContent": "sudo*" },
{ "toolName": "Bash", "ruleContent": "curl*|wget*" }
]
}
}
Q4: 怎么监控每天的 Token 消耗?
Claude Code 在每次会话退出时自动打印账单(如果你有 consoleBillingAccess)。更系统的做法是在项目级别建立追踪脚本:
# 在项目 .git/hooks/ 里加一个 post-checkout 钩子记录
# 但更实用的方法是直接看 .claude/cost/<session-id>.json
Q5: Skills 会不会每次都消耗额外 Token?
会,但可控。Skill 内容的 Token 占用被纳入了 POST_COMPACT_SKILLS_TOKEN_BUDGET(25K 上限)。Skill 越多越详细,上下文越大,所以 Skill 的编写原则是:精准匹配,最小必要信息。善用 paths 字段让 Skill 只在相关文件被访问时才加载。
Q6: 用了 Worktree 后 Claude Code 行为异常?
Worktree 有安全校验机制,如果 .git 文件格式不标准,Claude Code 会 fallback 到当前目录。检查 .git 文件内容是否为 gitdir: /absolute/path/to/.git/worktrees/<name> 格式。
十、扩展阅读 / 进阶方向
1. 编写自己的 Skill 减少重复工程
如果你发现自己在多个项目里反复写「按照这个风格规范写代码」这类 prompt,把它抽成一个 Skill,放到 ~/.claude/skills/ 下,全局生效。一次编写,多次节省。
2. 用 MCP 构建本地工具链替代云端调用
耗 Token 的操作往往是可以本地化的:代码搜索、文档查询、数据库操作。把这些能力封装成本地 MCP 工具,减少对 Claude API 的依赖,同时提升响应速度。
3. 监控 compact 日志持续优化上下文策略
开启 verbose 模式观察 compact 触发时机和压缩效果,持续调优 autoCompactThreshold 找到适合你项目规模的平衡点。
claude --verbose 2>&1 | grep -i compact
4. 从源码层面理解 Permission 分类器
Claude Code 的 auto 模式背后是一个 AI 分类器(Transcript Classifier),它根据操作内容判断风险等级。如果你想深入理解,可以研究 src/types/permissions.ts 里的 YoloClassifierResult 类型定义。