Claude Code Permission Budget Loop:5分钟检查权限、成本和日志
用一个实用循环检查 Claude Code 的 allow/deny 规则、成本上限、执行日志和团队交接。
为什么每天早上要检查5分钟
使用 Claude Code 时,真正的运营问题不只是“它会不会写代码”。更重要的是:“哪些事情可以让它不经确认就做?”每个 Bash 命令都手动批准,第一天看起来很谨慎,但很快就会出现审批疲劳。全部放开更危险:读取 .env、安装依赖、git push、生产 deploy、数据库 migration、billing 变更,都可能进入同一条通道。
Permission budget 是一张很短的运行表。它写清楚 Claude Code 哪些动作可以无需批准,哪些动作必须先问人,哪些动作在这个仓库里禁止。Loop 指的是每天早上把这张表和前一天的执行日志、使用量对照一遍。换句话说,就是每天数一遍交给 agent 的钥匙和预算。
截至 2026年6月3日,官方文档说明 Claude Code 权限使用 allow、ask 和 deny;deny 优先于 ask 和 allow。/permissions 会显示当前规则以及规则来自哪个 settings 文件。成本方面,/usage 适合看本地或当前 session 的情况,Claude Console 才是 billing 和 workspace limit 的权威来源。建议收藏 Configure permissions、Claude Code settings、Manage costs effectively 和 CLI reference。
内部延伸阅读可以连接到 Claude Code permissions guide、permission audit checklist 和 permission receipt pattern。
不用安全术语也能理解的基础
Claude Code permission 不是模型的承诺,而是 CLI 真正执行的边界。在 CLAUDE.md 写“不要读取 secret”是有用的指导,但它不是技术边界。Read(./.env) 或 Read(./secrets/**) 这样的 deny rule,才是 Claude Code 可以执行的边界。
Permission mode 也要分清。default 是标准审批流程。plan 更适合阅读和调查。acceptEdits 让文件修改更顺。dontAsk 会拒绝没有预先批准、也没有放进 ask 的工具。bypassPermissions,也就是 --dangerously-skip-permissions,会跳过权限提示,应该只在隔离 container 或 VM 里使用。
成本也一样。/usage 可以发现某个本地 session 是否异常昂贵,但 API billing 要在 Claude Console 确认。对于 claude -p 这样的脚本运行,--max-budget-usd 和 --max-turns 是有用的保护线,但不能代替团队预算。
每天的 permission budget loop
流程必须足够短,才会持续执行。目标不是完美审计,而是不要在危险权限仍然打开时开始一天的工作。
| 步骤 | 检查项 | 通过条件 |
|---|---|---|
| 1 | /permissions | 没有 Bash(*),也没有过宽的 Bash(npm *) |
| 2 | .claude/settings.json | secrets、deploy、database、billing 都在 ask 或 deny |
| 3 | /usage 和 Console | 昨天的成本增长可以解释 |
| 4 | git diff 和执行日志 | 已批准工作和 diff 一致 |
| 5 | 交接记录 | open allowances、blocked actions、next reviewer 都写清楚 |
可以用这个简短 prompt 开始:
Before starting today's Claude Code work, classify the task into:
1. safe to run without approval
2. requires human approval
3. should not run in this session
Then list up to five checks for /permissions, /usage, and git diff.
共享 settings.json 起点
下面是一个 .claude/settings.json 起点。defaultMode: "dontAsk" 很严格:不在 allow 或 ask 里的工具不会运行。先在本地试,再考虑作为团队共享设置。
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"defaultMode": "dontAsk",
"allow": [
"Bash(npm run lint)",
"Bash(npm run test)",
"Bash(npm run test *)",
"Bash(npm run build)",
"Bash(git status)",
"Bash(git diff)",
"Bash(git diff *)",
"WebFetch(domain:code.claude.com)"
],
"ask": [
"Bash(npm install *)",
"Bash(pnpm add *)",
"Bash(git push *)",
"Bash(wrangler deploy *)",
"Bash(vercel deploy *)",
"Bash(terraform apply *)",
"Bash(kubectl apply *)"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(rm -rf *)"
]
}
}
关键不是这份命令列表本身,而是把安全通道保持得足够窄。Bash(npm *) 可能从 test 滑到 install 或 publish。Bash(git *) 可能从 diff 滑到 push。读取、lint、test、build 可以窄范围 allow;install、push、deploy、apply 应该放在人类确认之后。
用 JSON 保留预算和执行日志
权限只是循环的一半,另一半是成本。长时间调查、反复失败的 test、后台 session、大段日志,都可能悄悄增加成本。把预算和每日日志写成 JSON,可以在 pull request 里看到。
{
"date": "2026-06-03",
"dailyLimitUsd": 6,
"warnAtUsd": 4,
"usageSource": "/usage plus Claude Console",
"safeAllow": [
"Bash(npm run lint)",
"Bash(npm run test)",
"Bash(git diff *)"
],
"askFirst": [
"Bash(npm install *)",
"Bash(git push *)",
"Bash(wrangler deploy *)"
],
"mustDeny": [
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
],
"handoffRequired": true
}
{
"date": "2026-06-03",
"spentUsd": 1.85,
"usageChecked": true,
"settingsChecked": true,
"permissionsReviewed": [
"/permissions",
".claude/settings.json"
],
"openAllowances": [
"Bash(npm run lint)",
"Bash(npm run test *)"
],
"handoff": [
"No deploy allowance left open",
"Claude stopped before production data work"
]
}
分别保存为 .claude/permission-budget.json 和 .claude/daily-claude-log.json。Spreadsheet 可以用于管理报表,但 JSON 更适合 code review 和自动化。
可直接运行的 Node 审计脚本
把下面内容保存为 scripts/audit-claude-loop.mjs,运行 node scripts/audit-claude-loop.mjs。不需要外部依赖。它会检查过宽 Bash 权限、deploy 命令是否误放进 allow、.env deny 是否缺失、预算是否超限、交接记录是否为空。
#!/usr/bin/env node
import fs from "node:fs";
const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
const budget = readJson(".claude/permission-budget.json");
const log = readJson(".claude/daily-claude-log.json");
const settings = readJson(".claude/settings.json");
const problems = [];
const permissions = settings.permissions ?? {};
const allow = new Set(permissions.allow ?? []);
const ask = new Set(permissions.ask ?? []);
const deny = new Set(permissions.deny ?? []);
const hasPattern = (items, pattern) => [...items].some((item) => pattern.test(item));
if (typeof budget.dailyLimitUsd !== "number" || budget.dailyLimitUsd <= 0) {
problems.push("dailyLimitUsd must be a positive number");
}
if (typeof budget.warnAtUsd !== "number" || budget.warnAtUsd >= budget.dailyLimitUsd) {
problems.push("warnAtUsd must be lower than dailyLimitUsd");
}
if (log.spentUsd > budget.dailyLimitUsd) {
problems.push(`spentUsd ${log.spentUsd} exceeds daily limit ${budget.dailyLimitUsd}`);
}
if (log.spentUsd >= budget.warnAtUsd) {
console.warn(`WARN: spentUsd ${log.spentUsd} has reached warnAtUsd ${budget.warnAtUsd}`);
}
if (!log.usageChecked) problems.push("Run /usage and mark usageChecked true");
if (!log.settingsChecked) problems.push("Review /permissions and mark settingsChecked true");
if (allow.has("Bash") || allow.has("Bash(*)") || hasPattern(allow, /^Bash\(\*.*\)$/)) {
problems.push("Do not allow every Bash command");
}
if (hasPattern(allow, /(deploy|terraform apply|kubectl apply|git push)/)) {
problems.push("Deploy, infrastructure, and push commands must be ask-first, not allow");
}
for (const rule of budget.askFirst ?? []) {
if (!ask.has(rule)) problems.push(`Missing ask rule: ${rule}`);
}
for (const rule of budget.mustDeny ?? []) {
if (!deny.has(rule)) problems.push(`Missing deny rule: ${rule}`);
}
if (!hasPattern(deny, /Read\(.*\.env/)) {
problems.push("Deny rules should block .env reads");
}
if (!Array.isArray(log.handoff) || log.handoff.length === 0) {
problems.push("Add at least one handoff note");
}
if (problems.length) {
console.error(problems.map((problem) => `- ${problem}`).join("\n"));
process.exit(1);
}
console.log("Claude Code daily permission budget check passed.");
放进 CI 时,建议先用 warning 或手动执行。第一天就设成强制 gate,往往只会让团队寻找绕过方式。
四个具体用例
第一个用例是文章和文档更新。Markdown、MDX、内部链接、CTA、错字、图片路径通常不会碰到 secret 或生产环境。把 file read、git diff、lint、test、本地 build 窄范围 allow,Claude Code 就能少停顿地生成小 diff。
第二个用例是依赖变更。npm install 和 pnpm add 会影响 lockfile、postinstall、license、vulnerability 和 bundle size。把它们放进 ask。批准前,让 Claude Code 写清为什么需要新依赖、是否有替代方案、如果不接受如何移除。
第三个用例是 deploy 和 migration。wrangler deploy、vercel deploy、terraform apply、kubectl apply、数据库 migration 都会改变外部状态。Claude Code 可以准备命令、影响范围、rollback、验证 URL、监控清单,但执行必须等人批准。
第四个用例是团队交接。如果别人稍后继续,需要写下 open allowances、proof commands、blocked actions 和 remaining budget。没有这条记录,下一位成员会重复调查、消耗更多 token,并重新打开同样的风险权限。
避免这些失败
最常见的失败是 Bash 权限太宽。Bash(npm *) 和 Bash(git *) 看起来方便,但会把只读习惯和改变状态的命令混在一起。日常通道尽量使用精确命令。
第二个失败是忘记关闭 deploy allowance。事故处理中,wrangler deploy * 可能临时从 ask 变成 allow。如果第二天还留着,普通 feature 工作也有了生产权限。
第三个失败是忽视 token 和成本增长。长时间调查看起来很有产出,但可能烧掉预算。检查 /usage,billing 重要时对照 Console,没有明确价值的 session 要停掉。
最后,不要把 prompt 当成 enforcement。“不要读取 secret”是指导。Read(./.env) 放在 deny 才是执行边界。Harness,也就是 agent 的工作框架,需要 prompt、settings、log 和 review 一起存在。
产品、培训和团队落地
个人练习时,先把 JSON 和脚本复制到一个小仓库。可复用 checklist、CLAUDE.md template 和 review prompt 可以看 /products/。如果团队需要权限、成本控制、CI policy、reviewer training 和按仓库定制的规则,可以看 /training/。
实践记录:最大的改善并不是把所有危险命令都封死,而是每天早上花5分钟看 /permissions、/usage、git diff 和交接记录。窄范围的 allow 仍然有用,而 deploy、billing、secrets 会稳定回到 ask 或 deny。
免费 PDF: Claude Code 速查表
输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。
我们会妥善保护你的信息,不发送垃圾邮件。
把 Claude Code 变成真正能带来结果的工作流
先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。
关于作者
Masa
专注 Claude Code 实务流程、团队导入和内容转化的工程师。
相关文章
Claude Code权限安全阶梯:逐步放开访问而不失控
从只读到有限编辑、验证命令和部署检查的 Claude Code 权限升级流程。
Claude Code 小PR证据包:让小改动真正可审查
用差异、验证命令、公开URL、CTA路径和回滚说明,把Claude Code的小PR变得可审查。
Claude Code 提交前 Review Gate:同时检查差异、测试、公开 URL 和 CTA
提交前用 Claude Code 审查差异范围、build、公开 URL、Gumroad 链接、咨询 CTA、缺少测试和无关文件。