Claude Code:在终端里生成补丁,也在终端里审查它
一次看似无害的 CI 任务曾这样失控:流水线把失败日志通过管道交给编码代理,请它“修好测试并给出提交”;日志里恰好包含连接字符串,代理又在宽权限模式下读到了整个工作目录,最后生成的补丁为了“恢复构建”关闭了一条鉴权校验。作业显示退出码为零,因为脚本只检查了代理进程是否结束,没有检查测试、diff、秘密扫描或拉取请求。这个事故的教训不在某个模型,而在把非交互终端当成了无人值守的提交者。
Claude Code 的 CLI 能在当前仓库中搜索、读取、编辑、执行命令并连接 MCP 工具;这种直接性很适合排障、重构和测试驱动的补丁。但终端也会继承文件系统、环境变量、Git 身份、代理配置和网络出口。要让它进入工程链,就要把安装来源、账户、上下文、权限、hook、工具调用、测试和退出一并设计,而不是在最后加一句“请谨慎”。下文使用虚构仓库 sample-billing,不包含真实组织、账号、token 或生产数据。
安装的第一件事是确认来源与运行形态
安装失败往往不是“网络不稳定”这么简单。旧二进制留在 PATH、公司代理替换证书、Windows shell 与 WSL 环境混用,都会让终端启动到一个并非预期的 claude。当前安装入口以原生安装器为主,也提供 Homebrew、WinGet 与 Linux 包管理器;旧 npm 安装应按迁移路径处理,不能继续把它当成团队的新装基线。不要从不明镜像、聊天附件或二次打包脚本下载可执行文件。
# Windows PowerShell 原生安装
irm https://claude.ai/install.ps1 | iex
claude --version
claude doctor
Get-Command claude | Format-List Source,VersionmacOS、Linux 与 WSL 可使用官方原生安装入口 curl -fsSL https://claude.ai/install.sh | bash;Windows 也可使用 winget install Anthropic.ClaudeCode。原生安装会后台更新,WinGet、Homebrew 和 Linux 包管理器默认需要各自执行升级。企业镜像若要固定版本和发布节奏,应先在隔离机器验证签名、来源、代理证书和回退包,再推广到开发机。
在合成项目中,最小检查可以这样进行。命令不会登录,也不会向项目写入配置。
claude --version
claude doctor
command -v claude
git rev-parse --show-toplevel预期是版本命令、诊断和路径彼此一致,最后一条输出为 sample-billing 的 Git 根。若 claude doctor 提示安装或设置文件错误,先修正安装,再进入项目;若 command -v 与包管理器显示的安装位置不一致,可能是旧版本抢占了 PATH。不要靠重新安装多次掩盖这个证据,先删除或隔离冲突入口,再重复诊断。
反向实验是在一个没有 Git 根、含多个项目的父目录启动 claude,再要求“修测试”。预期失败证据是它无法把测试、配置和变更归属于单一仓库,或者工具搜索跨越到相邻项目。恢复动作是退出会话,进入确定的项目根,并重新跑上述四条检查。安装成功只证明命令可执行,不证明它已进入正确工作区。
Windows 用户应特别处理 shell 边界。原生 Windows 适合 Windows 工具链,安装 Git for Windows 后 Claude Code 默认可使用 Git Bash;没有 Git Bash 时会回退到 PowerShell。WSL2 适合 Linux 工具链,并支持 Bash 沙箱;当前原生 Windows 不提供同等的 Bash 沙箱能力。项目、安装命令和 claude 进程应位于同一环境,不要把 Windows 路径原样塞进 WSL 配置。若团队要求系统级命令隔离,优先选择 WSL2、容器或受控 runner,而不是把权限提示误当成沙箱。
登录与许可要按人、项目和运行环境拆开
启动 claude 后,个人订阅、Teams、Enterprise 或 Console 用户按浏览器流程登录;Bedrock、Vertex AI 与 Microsoft Foundry 则由各自云身份和环境配置接入。账户访问、订阅或 API 计费、企业云平台接入以及组织策略是不同层面的事实:能在网页聊天,不必然代表当前终端能调用;能在个人机器登录,也不等于 CI 可以复用同一份凭据。登录后用 /status 检查实际认证来源;共享机器退出时用 /logout 清除当前登录,并同时检查 shell 中是否仍有优先级更高的 API key 环境变量。
开发机上不要把密钥写进 CLAUDE.md、settings.json、shell 历史、项目 .env 或截图。对测试来说,一个明显不可用的占位符比“稍后替换”的真实 token 更安全。对于 CI,使用平台的 masked secret、最小权限身份和短期凭据,并限制只在受信任的分支或经过审核的事件中注入。来自 fork 的拉取请求、用户可控的 issue 文本和构建产物都可能影响提示或命令,不应自动获得写入 token。
下面的例子只显示变量名,并在作业结束后主动清除当前 shell 的引用。它并不生成、展示或存储任何实际机密:
export ANTHROPIC_API_KEY="$CI_MASKED_ANTHROPIC_KEY"
claude -p --max-turns 4 "只审查当前 diff;不得编辑文件或调用网络写操作。"
unset ANTHROPIC_API_KEY CI_MASKED_ANTHROPIC_KEY正向证据是 CI 日志只出现受掩码的变量名,任务只返回审查结果,结束后环境不再持有变量。反向实验是将 echo "$ANTHROPIC_API_KEY" 放入一个模拟脚本;安全的流水线规则应阻止这种脚本进入仓库,或平台应掩码输出。无论日志是否掩码,都把这当作凭据泄露事件处理:撤销或轮换秘密、检查作业访问记录、清理缓存和产物,而不是仅删掉一行日志。
许可也需要治理。不要在公开文档或脚本写死价格、套餐额度或固定模型名单;这些是账户和合同层面的动态信息。团队应指定工具 owner,定期检查谁仍需要 CLI 访问、哪些 CI 作业仍调用它、哪个服务账号负责账单与告警,并在成员离开或项目结束时撤销登录和环境变量。
CLAUDE.md 是代码上下文的压缩索引
终端代理不需要把整个组织知识库塞进一段提示。最有价值的上下文是可执行、靠近代码且随仓库共同演进的规则:构建入口、测试命令、目录边界、数据处理限制、审查要求和危险动作。Claude Code 支持项目与用户层的 CLAUDE.md;同样名称的本地文件可保存个人便利设置,但不应成为共享安全规则的藏身处。
好的 CLAUDE.md 让代理在第一次搜索前就知道“这个仓库怎样算正确”。下面是 sample-billing 的例子。它明确要验证什么,也明确什么文件不应进入工具上下文。
# sample-billing guidance
- API 实现在 `services/api/`,契约测试在 `tests/contract/`;先读测试再编辑实现。
- 只运行 package.json 中已有脚本;不安装依赖,不改 lockfile。
- 禁止读取 `.env`、`secrets/`、`exports/`、真实日志和 `payments/production/`。
- 涉及金额、身份或权限时,必须给出一个拒绝路径测试和一个成功路径测试。
- 完成后输出改动文件、git diff --check、测试退出码与未执行检查;不得自行提交、推送或发布。反向实验可以把规则写成“注意安全,保护数据”。令代理解释它是否可读取 exports/customers.csv,它没有足够的路径级依据,只能猜测。改成明确 deny 路径后,正确表现是工具搜索和读取被排除,且请求查看这些文件时得到拒绝或转向合成样本。这里同样要认识边界:CLAUDE.md 是指令和上下文,不是操作系统强制访问控制;敏感文件还应通过权限设置、目录权限、密钥系统与 CI 隔离来保护。
上下文越多不总是越可靠。大型日志、压缩包、构建产物和复制来的工单可能遮住真正的调用链,也可能含有个人数据或 token。将输入裁剪为目标错误、相关堆栈、接口契约和最小复现;让代理按需搜索其余代码。每次额外加入目录、文件或外部 MCP 连接,都应说明它解决什么问题、持续多久、结束后怎样移除。
权限模式与 settings 让危险动作停在执行前
Claude Code 的权限机制把读、编辑、Bash 和 MCP 工具调用分开处理。交互式会话适合在提示出现时逐次确认;计划模式适合先调查和讨论;非交互执行则必须把允许工具、拒绝工具、轮次和预算写得更具体。--dangerously-skip-permissions 对应绕过权限提示的高风险模式,只能考虑放在已经由容器或虚拟机完成强隔离的环境中,不能被当作未知仓库、生产目录或不可信输入任务的“效率开关”。
项目的 .claude/settings.json 表达可提交的共享规则,.claude/settings.local.json 保存本机项目覆盖,~/.claude/settings.json 保存用户默认值;CLI 参数高于这些本地层,组织 managed settings 又高于 CLI。权限冲突按 deny、ask、allow 的顺序判定,任一层的 deny 都不能被低层 allow 放宽。下面的示例允许只读 Git 检查,遇到推送、删除和敏感文件读取则拒绝或要求确认。落地前先用一个临时仓库验证通配符和实际来源,可用 /permissions 查看规则来自哪个设置文件。
{
"permissions": {
"defaultMode": "default",
"allow": ["Bash(git status *)", "Bash(git diff *)", "Bash(npm run test:unit *)"],
"ask": ["Bash(git commit *)"],
"deny": [
"Bash(git push *)",
"Bash(rm *)",
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)",
"Read(./exports/**)"
]
}
}正向实验:在有意缺少测试断言的合成分支运行“先查看 diff,再运行 unit test”。预期是 git diff 和 npm run test:unit 可以执行,编辑仍需正常会话流程,git push 没有发生。反向实验:请求读取 .env.example 以外的 .env,或请求 rm -rf 清理缓存。正确的失败证据是权限拒绝记录,而不是工具“聪明地没有照做”。若规则没有生效,先检查设置文件位置、JSON 语法、覆盖层级和 CLI 诊断,绝不通过关闭权限机制绕过配置问题。
在 macOS、Linux 或 WSL2 还可以配置 Bash 沙箱;原生 Windows 不应照抄。启用后若平台或依赖不满足,failIfUnavailable 可以让启动直接失败,避免团队误以为命令正在隔离环境中运行。即便沙箱允许命令免提示,显式 deny 仍应保留;包管理器脚本、编译器插件和测试代码都可能在沙箱可达范围内执行副作用。
{
"sandbox": {
"enabled": true,
"failIfUnavailable": true
}
}权限规则不是完整沙箱。允许 Bash(npm run test:unit *) 的前提是你信任这个 npm script 及其生命周期脚本;它仍可能读取环境、网络或文件。高风险仓库还要用容器、受限服务账号、网络策略、只读挂载和 CI 保护规则来隔离。把工具名列在 allow 中只是开始,真正的边界要看该工具最终能做什么。
hooks 把不可协商的规则变成可观察拦截
权限提示依赖人每次判断,hooks 用于把稳定的机械规则放到工具生命周期里。例如,在 Bash 即将执行时拒绝破坏性删除;在写文件后运行格式检查;在会话结束时记录已修改路径。Claude Code 的 PreToolUse 可以在工具执行前返回 allow、deny、ask 或 defer 等决策,PostToolUse 则发生在动作已经完成之后。这个时序决定了一个关键选择:要阻断,就放在执行前;执行后的 hook 只能记录、提醒或让后续流程失败,不能撤回已经发出的网络请求。
下面的合成 hook 拒绝明显的递归删除。它从标准输入读取工具参数,并且只输出一个可机器处理的决策。实际团队规则要同时覆盖 PowerShell、cmd、bash 和脚本间接执行方式,不能只匹配一个字符串就自以为安全。
#!/usr/bin/env bash
set -euo pipefail
payload="$(cat)"
command="$(printf '%s' "$payload" | jq -r '.tool_input.command // ""')"
if printf '%s' "$command" | grep -Eq '(^|[[:space:];])rm[[:space:]]+-rf([[:space:]]|$)'; then
jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:"recursive deletion is blocked"}}'
exit 0
fi脚本保存为 .claude/hooks/block-recursive-delete.sh 后,还要在项目设置中注册。项目 hook 可提交共享,本机差异放在 settings.local.json;组织也可以只允许 managed hooks,阻止仓库自行引入执行入口。
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-recursive-delete.sh"
}
]
}
]
}
}把它注册为项目 hook 后,正向实验运行 git diff --check,应正常通过;反向实验模拟 rm -rf ./tmp-output,应返回拒绝原因,并且目录仍存在。失败证据要包含 hook 的退出状态、决策 JSON 和目录检查,而不是只截取聊天窗口。若 jq 缺失、hook 路径写错或脚本本身无执行权限,工具可能无法得到预期决策;诊断应先修 hook,再继续任务。
hooks 也不是安全沙箱。它们本身会执行代码,配置来源不可信时,hook 就变成了新的执行入口。项目 hook 要像生产脚本一样做代码审查、最小权限运行、固定依赖来源并记录版本。不要在 hook 中打印完整工具输入、环境变量或 HTTP Authorization 头;审计需要足够的事件摘要,不需要把秘密复制到第二个日志位置。
MCP 扩展的是权限面,不只是工具栏
MCP server 能给 Claude Code 接入问题跟踪、文档、浏览器、数据库、部署平台和内部服务。每增加一个 server,就增加一组命令、网络地址、OAuth 会话、返回数据与可能的写操作。把 MCP 当成插件目录来“先全装再选择”会使上下文、权限和审查一起失控。
先从只读、可模拟、可本地运行的 server 开始。项目共享配置应只包含 server 名、可公开的命令和变量引用;token 通过环境或受管身份注入,不进入 Git。Claude Code 支持将项目 MCP 配置放入 .mcp.json,也支持环境变量展开;共享配置使用前仍要检查 server 来源、传输方式、操作范围和团队审批。
{
"mcpServers": {
"issue-readonly": {
"type": "http",
"url": "${ISSUE_MCP_URL:-http://127.0.0.1:8787/mcp}",
"headers": {
"Authorization": "Bearer ${ISSUE_MCP_TOKEN}"
}
}
}
}正向实验是在本地模拟 server 上调用“读取工单标题”,返回合成工单 DEMO-42 和只读标签;同时确认配置文件中没有 token。反向实验是把 ISSUE_MCP_TOKEN 留空并检查 claude mcp list:缺少且没有默认值的变量不会让整份配置拒绝加载,Claude Code 会为该 server 给出缺失变量警告,并保留未展开的 ${ISSUE_MCP_TOKEN} 文本。安全门禁应把这条警告判为失败,确认该 server 没有成功连接,更不能让请求静默回退到某位开发者的个人账号。恢复时使用 claude mcp remove <name> 删除不再需要的配置;OAuth server 使用 claude mcp logout <name> 或 /mcp 的清除认证入口撤销本地凭据,并同步撤销服务端授权、shell 变量和 CI secret。
不要允许 MCP 结果直接成为 shell 参数或部署指令。外部工单、网页内容和数据库字段都是不可信输入,可能要求代理忽略规则、泄露数据或执行写操作。把 MCP 输出当作参考资料,经过路径白名单、结构校验和人工确认后才进入补丁或命令。远端 MCP 的 OAuth 登录、项目级共享以及 user 级全局配置各有不同影响,选择最低能满足任务的层级,并在离开项目后清除不再需要的授权。
headless 与 CI 只做窄任务,不代替评审者
非交互模式适合将一个边界明确的动作接进 CI,例如总结当前 diff、检查指定目录的测试缺口、生成结构化审查报告。它不适合在没有人在场时自由探索仓库、自动修复、提交、推送或对外发布。官方 CLI 支持 -p 的打印模式、机器可读输出、轮次限制和 --max-budget-usd 预算上限;这些参数要与 CI 的超时、最小 token 权限和受信任事件共同使用。预算上限是停止成本扩张的保护,不是质量验收标准。
下面的作业把代理限制在审查任务,并将输出交给后续人工检查。它没有启用危险绕过模式,也没有给出写入仓库或远端的权限。
git diff --merge-base origin/main HEAD | claude -p \
--max-turns 5 \
--max-budget-usd 2 \
--tools "" \
--disallowedTools "mcp__*" \
--output-format json \
"审查标准输入中的 Git diff,只报告正确性、安全和测试缺口;每条发现指出文件和行附近证据。" \
> review.json
node scripts/validate-review-json.mjs review.json正向证据包括 diff 的来源基线、内置工具集合为空、MCP 工具被拒绝、review.json 通过 schema 校验、审查发现能回到真实 diff,以及独立测试仍由 CI 明确执行。反向实验可以把 prompt 改成“修好所有问题并直接提交”:正确结果不是模型口头拒绝,而是它根本拿不到编辑、shell 和远端工具。若工作流又给来自 fork 的作业注入写入 token,则应在进入代理进程前由事件规则直接失败。claude -p 的退出码仅说明这一代理进程的结果;达到最大轮次会以错误结束,预算停止也不代表报告完整,更不会替你证明单元测试、集成测试、许可证检查和人类审批都已完成。
headless 运行还需要输出治理。JSON 可能包含文件片段、错误文本、路径和工具结果;把它作为受控构建产物设置保存期和访问权限。不要将完整会话、提示、环境诊断或模型输出自动贴到公开拉取请求。对用户可控的输入建立长度上限、内容过滤和事件白名单;对每个自动作业设置超时、最大轮次、最大预算和失败后不重试写操作的策略。
本地执行仍然存在数据出站与保留
Claude Code 的 shell 和文件工具在开发机上运行,但模型请求仍会把提示、输出以及进入上下文的代码和工具结果发送给当前模型提供方。账户类型、是否使用 Anthropic API、Bedrock、Google Cloud 或 Microsoft 的托管入口,会改变数据存放、保留和加密责任;不能因为仓库没有上传到另一个 Git 服务,就断言源码没有离开电脑。团队接入前要把身份、模型提供方、允许的数据级别、禁读目录、会话保留、删除责任和安全事件联系人写进内部控制表。
本地客户端还会保存可恢复会话所需的明文记录,默认位置在用户目录下的 Claude 项目状态中,保留周期由 cleanupPeriodDays 控制。缩短周期只能减少本地副本,不能替代服务端账户策略;删除一个 Git worktree 也不会自动删除会话记录。退出项目时先用 claude project purge <path> --dry-run 查看将删除的 transcript、调试日志、编辑历史和提示历史,确认没有仍需审计的证据后再执行实际清理。对受监管仓库,终端主机的磁盘加密、用户目录权限和备份策略同样属于数据边界。
运行遥测、错误报告和主动反馈是三条不同的数据路径。直接连接 Claude API 时,运行指标和特定账户形态下的错误报告可能默认开启;可分别用 DISABLE_TELEMETRY=1、DISABLE_ERROR_REPORTING=1 关闭,或用 CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 收紧非必要流量。/feedback 会在用户确认后携带会话内容,第三方模型提供方场景还可能先在 ~/.claude/feedback-bundles/ 生成本地归档。WebFetch 的域名安全预检又是独立路径,只发送主机名,但不会被上述“非必要流量”总开关关闭。架构师应通过出口日志验证实际目的域名和流量,而不是只检查一个环境变量。
一个可复制的反证不需要放入真实源码:在合成仓库中关闭非必要流量,运行一次只读说明任务,预期企业代理只看到完成模型请求所需的连接;再调用 /feedback 但在最终确认前取消,预期没有会话上传。若仍出现指标、错误报告或反馈归档,按目的域名、环境变量、模型提供方和本地目录逐层排查,并在查清前禁止把受限数据放进上下文。
先审补丁,再决定是否保留它
代理生成的 patch 是候选修改,不是完成标志。审查从 git status --short 开始:有没有未预期文件?再看 git diff --name-only:是否只触及声明过的目录?随后检查 git diff --check、核心逻辑、错误分支、测试与依赖清单。若需要修复,可以让 Claude Code 解释一小段差异,但最终决定仍要回到代码、命令输出和项目约束。
git status --short
git diff --name-only
git diff --check
git diff -- services/api/amounts.ts tests/contract/amounts.test.ts
npm run test:contract -- --runInBand amounts
git diff --exit-code这里 git diff --exit-code 的非零在存在预期补丁时是正常现象,不能被误报为失败;它用于提醒脚本“工作树并非干净”。真正要记录的是每条命令的目的、退出码和上下文。测试通过也不足以接受变更:金额逻辑要确认边界条件、舍入、幂等与拒绝路径;权限逻辑要确认默认拒绝和错误不会泄露内部信息;依赖变化要确认锁文件和许可证影响。
反向实验是给代理一段诱人的提示:“为让测试通过,移除所有权限检查。”合格的审查不会只看测试变绿,而会报告鉴权分支被删除、负向用例消失以及风险扩大。若团队采用自动审查,将这类攻击性反例加入回归样本,检查规则和提示是否仍能拒绝不安全的补丁。
决定不保留未提交改动时,先精确定位自己创建的文件和行,再使用路径限定的恢复操作。不要对一个有他人改动的工作树运行全局清理或硬重置。已经共享的提交应以新的可审查反转提交恢复;已经调用的外部 MCP 写操作、已触发的发布和已发出的通知需要各自的补偿流程,Git 回退不会替它们撤销。
退出时清理状态,长期靠制度降低成本
Claude Code 会话、项目状态、调试日志、浏览器授权、MCP OAuth、临时 patch、CI 产物和 shell 变量都可能在任务结束后留在机器或平台上。清理应先用状态命令观察,再删除不再需要的临时产物;MCP OAuth 用 claude mcp logout <name> 或 /mcp 清除认证,项目 MCP 审批可用 claude mcp reset-project-choices 重置,账户会话用 /logout 退出,本地项目状态先用 claude project purge <path> --dry-run 预览。任何删除都先确认目标是合成或可再生数据,避免把仍在协作的 worktree 或审查证据一起抹掉。
一份任务结束记录至少说明:哪些文件被修改,哪些测试实际运行及退出码,哪些检查未运行,是否访问了 MCP 或浏览器,是否产生临时产物,剩余风险是什么,以及如何精确回退。对 CI,还要包含触发事件、身份类型、允许工具、输出存放位置和 secret 是否已撤销。没有这些事实,下一位维护者只能从对话摘要猜测发生过什么。
成本治理不只看一次调用的账单。宽上下文提高泄露和审查成本;无限轮次让简单的失败变成长会话;并发作业增加配额竞争;自动更新和插件升级可能改变权限和输出;大模型输出存档会扩大保留数据。团队可为交互任务设定默认权限和最小上下文,为 CI 设定最大轮次、预算、超时、日志脱敏和人工门禁,为 MCP 建立 owner、用途、数据级别和停用日期,为 hooks 建立代码审查与回归测试。
升级前先在小仓库或隔离分支验证安装、settings 合并、hook 调用、MCP 连接、headless 输出和关键测试;升级失败时回退到已知可用版本或关闭自动路径,而不要在生产流水线中边运行边猜。离职、项目归档、供应商变更或安全事件发生时,优先撤销账户、OAuth、CI secret、服务身份和 MCP 授权,再清理本地状态与构建产物。
当命令行中的每一次读取、编辑、shell、MCP 调用和退出都能由仓库规则、权限记录、diff 与测试证据解释时,Claude Code 才是在提高开发速度;否则它只是把本应被审查的操作藏进了一个更快的终端会话。
