1. 为什么你的 Claude Code 需要一个 Bash 守门员刚接触 Claude Code 的开发者大概率都经历过这样一幕让模型帮忙整理一下项目里的临时文件结果它反手来一句rm -rf ./tmp你还没来得及点确认命令已经跑完了。更隐蔽的是cat config.json这种写法它看起来人畜无害实际上会直接覆盖掉你辛苦调好的配置文件而且完全绕过了 Write 工具那套「先读再写」的保护机制。Claude Code 本身提供了工具权限审批但默认的审批粒度比较粗很多时候你点了「总是允许」之后后续同类命令就一路绿灯了。这时候就需要 Hook 机制出场。Hook 是 Claude Code 在工具调用生命周期里预留的拦截点其中 PreToolUse 事件会在工具真正执行之前触发你可以在这里塞一段自己的检测逻辑命中危险模式就强制弹窗让模型停下来问你一句。这篇内容面向的是刚接触 Hook 机制、还没搞明白 settings.json 该怎么写的开发者。我会把 PreToolUse Hook 拦截 Bash 命令的完整配置拆开讲包括全局和项目级两种作用域的差异、检测脚本的交互协议、以及一次真实的触发验证过程。你跟着做下来能拿到一个可复制的危险命令防护方案也能理解 Hook 在工具调用前后到底做了什么。核心检索词先摆出来Claude Code Hook 配置、PreToolUse 拦截 Bash、settings.json 写法、危险命令防护。这几个词贯穿全文你搜到的其他教程如果只讲了概念没给可跑通的配置那基本没法直接用。需要说明的是Hook 不是万能的。它本质上是「在命令执行前插一段你自己的判断」判断逻辑写得好不好直接决定它是帮你挡刀还是天天误报烦你。所以我会重点讲清楚匹配顺序、排除规则、以及 fail-open 还是 fail-close 的取舍这些才是避坑的关键。2. TaoToken 前置准备让 Claude Code 稳定跑起来在折腾 Hook 之前得先保证 Claude Code 本身能正常调用模型。很多新手卡在第一步——环境通了但模型请求一直 401后面配 Hook 自然无从谈起。我自己的做法是先把模型接入层理顺再动 Hook 配置这样出问题能快速定位是接入层还是 Hook 层。TaoToken 在这里扮演的是模型接入网关的角色它提供统一的 API 入口Claude Code 通过它来请求 Claude 系列模型。你需要准备三样东西Base URL、API Key、Model ID。这三件套在后面的 settings 配置里会反复出现先记牢。Base URL 用https://taotoken.net/api注意这个地址不带任何查询参数直接填就行。API Key 需要到控制台生成路径是 API Keys 管理页生成后复制保存它只显示一次。Model ID 则根据你要用的模型填比如 Claude 系列的对应标识。如果你用的是 Claude Code 的官方客户端接入配置通常写在环境变量或者项目配置里。我建议先在终端里做一次最小验证确认模型能通再去配 Hook。验证方式很简单用 curl 直接打一次对话接口curl https://taotoken.net/api/v1/messages \ -H Content-Type: application/json \ -H x-api-key: 你的API_KEY \ -H anthropic-version: 2023-06-01 \ -d { model: 你的Model_ID, max_tokens: 64, messages: [{role: user, content: ping}] }返回里能看到content字段有内容说明接入层通了。这一步别跳过因为后面 Hook 触发时如果模型请求本身就不通你会误以为是 Hook 把命令拦死了白白排查半天。对于长期做编码和 Agent 任务的场景可以考虑用 Coding Plan它在用量和稳定性上更适合高频调用。如果你只是想先验证模型对话是否正常用模型对话页面直接试一句也行。接入文档里有各客户端的详细配置说明遇到字段对不上可以去翻。把接入层跑通之后Claude Code 的会话就能正常工作了。接下来才是本文的重点在工具调用链路上挂一个 PreToolUse Hook专门盯 Bash 命令。3. 可复制配置settings.json 里的 PreToolUse Hook 怎么写Claude Code 的配置文件分三个层级优先级从低到高分别是全局、项目、项目本地。全局配置在~/.claude/settings.json对所有项目生效项目配置在.claude/settings.json会提交到 git 给团队共享项目本地配置在.claude/settings.local.json通常加进 gitignore只影响你自己。我推荐把危险命令检测放在全局层这样不管你打开哪个项目防护都在。下面这份就是可以直接复制的全局配置片段路径和原文保持一致{ hooks: { PreToolUse: [ { matcher: Bash, hooks: [ { type: command, command: python \$HOME/.claude/hooks/check-dangerous-bash.py\, timeout: 5, statusMessage: Checking command safety... } ] } ] } }逐字段拆一下。hooks是所有钩子的容器。PreToolUse是事件类型表示在工具执行前触发这个事件能拦截。matcher填Bash表示只匹配 Bash 工具调用其他工具如 Read、Write 不会走到这个 Hook。type填command表示执行一段 Shell 命令另外还有prompt、agent、http三种类型分别对应调 LLM 判断、起子 Agent、POST 到外部服务成本和复杂度都更高规则明确的场景用command最划算。command里调用 Python 脚本路径用$HOME/.claude/hooks/。这里有个坑全局配置不能用$CLAUDE_PROJECT_DIR那个变量指向项目根目录只在项目级配置里有意义。如果你在全局配置里写了$CLAUDE_PROJECT_DIR脚本路径会解析失败Hook 静默不生效你还以为防护开着。timeout设 5 秒防止脚本卡死阻塞整个工具调用。statusMessage是执行时 spinner 旁边显示的提示文字给个「Checking command safety...」让用户知道在检测。如果你只想在某个项目里生效把同样的结构写进.claude/settings.local.json但command路径要换成$CLAUDE_PROJECT_DIR/.claude/hooks/check-dangerous-bash.py。两种作用域的差异就这一点别搞混。配置写完后检测脚本本身也得放对位置。全局方案下先建目录mkdir -p ~/.claude/hooks然后把check-dangerous-bash.py复制进去。脚本的核心逻辑是读 stdin 的 JSON、提取命令、匹配危险模式、输出决策 JSON。关键设计是命中危险模式时输出permissionDecision: ask弹确认框而不是硬阻断这样真正需要的操作还能放行。匹配顺序用 if/elif 链命中即停避免一条命令触发多条警告。排除规则上追加重定向、21合并 stderr、2这些都要跳过它们不覆盖文件内容拦了就是误报。脚本解析失败时输出{}放行这是 fail-open 策略——宁可漏过也不误阻断。这个取舍很重要如果 fail-close脚本一有 bug 你所有 Bash 命令都跑不了开发直接停摆。4. 验证请求一次 PreToolUse 触发与日志确认配置写完不验证等于没配。下面走一遍完整的触发流程你能亲眼看到 Hook 在工具调用前做了什么。先确认脚本有执行权限虽然用python显式调用不强制要求但养成习惯没坏处chmod x ~/.claude/hooks/check-dangerous-bash.py然后新开一个终端进入任意项目目录启动 Claude Code。在对话里让它执行一条危险命令比如帮我在项目根目录执行 cat test.txt正常情况下你会看到 spinner 旁边闪过「Checking command safety...」紧接着弹出权限确认框里面显示拦截原因大意是cat file会覆盖写入、绕过 Write 工具保护。这就是 PreToolUse Hook 生效了。如果你想脱离 Claude Code 单独测脚本逻辑可以直接喂 JSON 给脚本echo {tool_name:Bash,tool_input:{command:cat test.txt}} | python ~/.claude/hooks/check-dangerous-bash.py输出应该是一段带permissionDecision: ask的 JSON。再测一条安全命令echo {tool_name:Bash,tool_input:{command:ls -la}} | python ~/.claude/hooks/check-dangerous-bash.py输出是{}表示放行。这两条一对比脚本的判定逻辑就清楚了。再测几个边界情况。echo hi log.txt应该放行因为是追加不覆盖。rm -rf node_modules应该被拦命中rm -r模式。dd if/dev/zero ofdisk.img应该被拦。some-cmd 21应该放行因为只是合并输出流。验证通过后记得把测试文件删掉。整个流程走下来你应该能感受到 Hook 的定位它不是替代你的判断而是在模型动手之前把危险动作拎出来让你确认一次。日志层面Claude Code 的调试日志里能看到 Hook 的调用记录如果没触发先查脚本路径和matcher字段这两个是最常见的失效原因。5. 本篇常见错排查401、local proxy failed 与 reading choices配 Hook 的过程中报错往往不在 Hook 本身而在接入层或者配置格式。下面几个是我和身边人踩过的坑对照着排。401 未授权。这个基本是 API Key 的问题。检查三件事Key 有没有复制完整、有没有多余空格、请求头字段名对不对。Claude 系列用的是x-api-key不是Authorization: Bearer。如果你在 settings 里配了 Key 但 Claude Code 还是 401去控制台的 API Keys 页面确认这个 Key 还在有效期内没被删。local proxy failed。这个报错通常出现在网络层意思是本地代理连接失败。先确认你的 Base URL 填的是https://taotoken.net/api没有多写路径或者参数。然后检查环境变量里有没有残留的代理设置比如HTTP_PROXY、HTTPS_PROXY这些如果指向一个已经关掉的本地端口就会报 local proxy failed。清掉这些变量再试。reading choices 相关报错。这类错误一般出现在响应解析阶段说明返回的 JSON 结构和你客户端预期的对不上。常见原因是 Model ID 填错了或者请求体里messages格式不对。用第 2 节那条 curl 命令先验证原始返回确认content字段存在再去查客户端配置。Hook 不触发。如果命令跑了但没弹窗按顺序查matcher是不是Bash、脚本路径在全局配置里有没有误用$CLAUDE_PROJECT_DIR、脚本有没有语法错误导致解析失败后 fail-open 放行。用第 4 节的 echo 管道单独测脚本能快速定位是脚本问题还是配置问题。OAuth 相关报错。如果你用的是需要 OAuth 的客户端报错里出现 OAuth 字样通常是认证流程没走完或者 token 过期。重新走一遍登录流程确认回调地址和客户端配置一致。CC Switch / Cline MCP / Codex auth.json 场景。如果你在这些工具里配置 Claude Code 接入三件套必须写全Base URL 填https://taotoken.net/apiKey 填控制台生成的Model ID 填对应模型标识。少任何一个都会报错尤其是 Model ID 漏填时有些客户端会默认用一个不存在的模型名报错信息还很隐晦。排障的核心思路是分层先确认接入层通curl 能返回内容再确认 Hook 层通echo 管道能输出决策 JSON最后确认集成层通Claude Code 里能触发弹窗。一层一层来别跳。6. 把防护变成习惯从一次配置到长期可用Hook 配好只是开始真正让它有价值的是持续维护检测规则。危险模式不是一成不变的你今天拦住了rm -rf明天可能遇到find . -delete这种同样具有破坏性但没被覆盖的写法。所以脚本里的正则列表需要定期补充。我的做法是每次遇到一次「差点出事」的命令就回头把它加进检测规则。比如truncate -s 0 file会清空文件shred会覆写文件这些都可以纳入。修改脚本后不需要重启 Claude Code下次 Bash 调用即刻生效这点很省事。另一个实用技巧是把permissionDecision从ask改成deny用于极危险模式。ask是弹窗让你决定deny是直接拒绝执行。对于dd if这种一旦执行就可能毁盘的命令直接 deny 更稳妥。你可以在脚本里对不同模式用不同决策分级处理。如果你团队里多人用 Claude Code把项目级配置.claude/settings.json提交到仓库配合项目内的检测脚本能让整个团队共享同一套防护规则。个人偏好放.claude/settings.local.json不污染团队配置。长期做编码和 Agent 任务的话接入层的稳定性同样重要。Coding Plan 在用量和并发上更适合高频场景配合 Hook 防护基本能做到「模型放手干活危险动作有人兜底」。需要生成 Key 或者查接入细节去 API Keys 页面和接入文档转一圈配置字段对不上时那里有权威说明。最后留一句实在话Hook 是护栏不是替身。它能拦住已知的危险模式但拦不住你没预料到的写法。真正安全的做法是保持对模型输出的审视尤其是涉及文件写入和删除的操作弹窗出现时别条件反射点允许。
阅读完成 · 觉得有帮助?