工具大全
Claude 指南作者:Coocon2026年4月13日803 次阅读约 12 分钟阅读

Claude Code Hooks:自定义自动化工作流

这篇文章原本只讲原理。2026-09-19 我补了文末的「本机实测」一节:在 Claude Code 2.1.270 上真搭了一个隔离的 hook 实验室,8 类事件全挂同一个脚本,把 stdin JSON 原样落盘,再用「hook 标记文件 + 工具标记文件」双向确认工具到底跑没跑。先说结论:上面这篇正文里有三处写错了——只有 exit 2 会拦住工具(exit 1 和 exit 3 一律放行)、单数 "hook": {…} 这个配置键已经被静默忽略、$CLAUDE_FILE_PATH / $CLAUDE_TOOL_INPUT 两个环境变量在 2.1.270 上根本不存在。 原文保留在下面不动,实测一节逐条给出真实读数与修正写法。

什么是 Hooks

Hooks 是 Claude Code 提供的自动化扩展机制,允许你在特定事件发生时自动执行自定义脚本。通过 Hooks,你可以在 Claude 调用工具之前或之后插入自己的逻辑,比如自动格式化代码、阻止危险命令或发送通知。

Hooks 在你的本地机器上以确定性方式执行,不消耗 LLM 的 token,是构建可靠自动化工作流的理想方式。

Hook 事件类型

事件 触发时机
PreToolUse 工具调用执行之前
PostToolUse 工具调用执行之后
Notification Claude 发送通知时
Stop Claude 完成响应时

配置方法

Hooks 在 .claude/settings.json 中配置:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hook": {
          "type": "command",
          "command": "echo 'File is about to be modified'"
        }
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hook": {
          "type": "command",
          "command": "npx prettier --write $CLAUDE_FILE_PATH"
        }
      }
    ]
  }
}

matcher 字段支持正则表达式,用于匹配工具名称(如 Edit、Write、Bash 等)。

实战示例

文件保存后自动 Lint

{
  "PostToolUse": [
    {
      "matcher": "Edit|Write",
      "hook": {
        "type": "command",
        "command": "npx eslint --fix $CLAUDE_FILE_PATH 2>/dev/null || true"
      }
    }
  ]
}

阻止危险命令

{
  "PreToolUse": [
    {
      "matcher": "Bash",
      "hook": {
        "type": "command",
        "command": "if echo \"$CLAUDE_TOOL_INPUT\" | grep -qE 'rm\\s+-rf\\s+/|DROP\\s+DATABASE'; then echo 'BLOCKED: Dangerous command detected' >&2; exit 1; fi"
      }
    }
  ]
}

当 hook 脚本以非零状态退出时,对应的工具调用将被阻止。

完成时发送通知

{
  "Stop": [
    {
      "matcher": "",
      "hook": {
        "type": "command",
        "command": "osascript -e 'display notification \"Claude 完成了任务\" with title \"Claude Code\"'"
      }
    }
  ]
}

最佳实践

  • 保持脚本快速:Hook 脚本会阻塞 Claude 的执行流程,避免长时间运行的任务
  • 妥善处理错误:使用 || true 防止非关键脚本失败阻塞工作流
  • 善用环境变量:Claude 会注入 $CLAUDE_FILE_PATH、$CLAUDE_TOOL_INPUT 等上下文变量
  • 团队共享:将 hooks 配置放在项目级 .claude/settings.json 中,提交到仓库供团队共用

常见问题

Hooks 和 MCP 有什么区别?

Hooks 是确定性的本地脚本,在特定事件时自动触发,适合自动化检查和格式化。MCP 则是让 Claude 访问外部工具和数据源的协议,Claude 可以主动调用 MCP 提供的工具。两者互补,不冲突。

Hook 脚本执行失败会怎样?

如果 PreToolUse 的 hook 脚本以非零状态退出,对应的工具调用将被阻止。如果 PostToolUse 的脚本失败,Claude 会收到错误信息但会继续执行后续操作。

可以在 Hook 中使用哪些环境变量?

Claude Code 会自动注入上下文相关的环境变量,包括 CLAUDE_FILE_PATH(当前操作的文件路径)、CLAUDE_TOOL_INPUT(工具的输入参数)等。具体可用变量取决于触发的事件和工具类型。


本机实测:8 类事件、exit code、matcher 和延迟,在 2.1.270 上真跑一遍

问题背景

上面这篇原文是按官方文档写的,能跑通的读者不少,跑不通的也不少——而「跑不通」在 hooks 这件事上特别难查:配置写错了不报错,hook 没触发不报错,脚本退出码不对也不报错,你只会看到「好像没生效」。

我自己在这个坑里栽过一次,结论写在另一篇里:Claude Code Hooks 的 stdin 陷阱(那篇讲的是 hook 脚本内部读 stdin 的方式会静默失效,和本篇不重合)。这次我想把外部契约整个量一遍:到底哪些事件会触发、hook 收到的 JSON 长什么样、退出码各自意味着什么、matcher 怎么写才命中、挂 hook 要付多少延迟。

问题分析

要把这些问题变成可验证的读数,有三个障碍:

  1. 「hook 没触发」和「hook 触发了但没生效」在日志上长得一样。 只看 hook 自己写的日志,无法区分 PreToolUse 是「没跑」还是「跑了但没拦住」。
  2. 本机已经有两个生产用的 hook(全局的 ~/.claude/hooks/log_tool_io.py、本仓库的 .claude/hooks/lessons-inbox-capture.sh),实验绝对不能污染它们。
  3. 每次 claude -p 冷启动都要烧掉五万个 cache_creation token,在 opus 上一次约 $0.32,几十次实验会很贵。

技术方案与选型

最终方案:独立 CLAUDE_CONFIG_DIR + --settings <绝对路径> + 每次实验独立工作目录 + Haiku 驱动 + 双向标记文件。

  • 隔离方式选 --settings 而不是写 .claude/settings.json:--settings 接一个任意路径的 JSON,不落在任何被扫描的目录里,实验配置和生产配置物理隔离;同时 CLAUDE_CONFIG_DIR 指到 tmp/ 下的一个空目录,用户级 hook 也进不来。(排除项:直接改仓库的 .claude/settings.json——会污染生产 hook,且每次改完要记得还原,不可接受。)
  • 双向标记文件是关键设计:hook 脚本每次触发写一行到 hook-ran.txt,同时让模型执行的 Bash 命令写一个 tool-ran.txt。两个文件一交叉,「hook 触发了几次」和「工具到底执行没执行」就是两个独立的事实,不用猜。(排除项:只看模型的自然语言回复——模型会脑补「我已经执行了」,不可信。)
  • 驱动模型选 Haiku 4.5:38 次会话总共 $1.5248,平均 $0.04/次。(排除项:直接打 /v1/messages API 自己造 hook 场景——本机的 Anthropic 代理只放行 Claude Code 客户端,脚本直连一律 503,这条路根本走不通。)
  • --output-format json 取时间与成本:duration_ms / num_turns / total_cost_usd 都是 CLI 自己吐的,不用我掐表。端到端 wall_s 另外用 shell 记录,两个数一起看。

每次实验的形状固定成这样:

claude -p "<最小任务>" \
  --settings /abs/path/to/lab/xxx.settings.json \
  --output-format json \
  < /dev/null          # 不加这行会等 3 秒 stdin,把延迟实验全毁了

实测一:8 类事件里,哪几类真的会触发

一个最小任务(Read 一个文件 → Write 一个文件 → echo lab-probe),8 类事件全部挂同一个 capture.sh,把 stdin JSON 原样追加进 events.jsonl:

Claude Code 8 类 hook 事件实测触发时间线

主会话一共触发 9 次,顺序和时间戳(相对第一次触发):

时刻 事件 tool_name
0.000s SessionStart —
0.024s UserPromptSubmit —
6.095s PreToolUse Read
6.122s PostToolUse Read
9.374s PreToolUse Write
9.400s PostToolUse Write
9.450s PreToolUse Bash
9.573s PostToolUse Bash
11.933s Stop —

注意 PreToolUse → PostToolUse 的间隔只有 26~123ms,中间夹的是工具真实执行时间;而两次工具调用之间隔了 3.2 秒,那是模型在思考。hook 的开销要和「模型思考」这个量级比,不是和「工具执行」比——这条后面延迟实验会再印证。

剩下三类事件要另外的会话才能逼出来。让模型起一个后台子代理:

时刻 事件 备注
8.992s PreToolUse tool_name 是 Agent,不是文档里常见的 Task
11.164s Stop 主会话先结束了
13.234s SubagentStop agent_type=general-purpose
13.344s UserPromptSubmit prompt 内容是 <task-notification>…,机器生成的 prompt 也走这个事件
16.663s Stop 第二次 Stop

这两条是文档里没有的:想用 matcher 匹配子代理调用,要写 Agent;想在 UserPromptSubmit 里做「用户输入审查」,必须准备好收到不是人打的 prompt(否则后台任务一回来就被你自己的规则拦下)。

再 --resume 一个会话手动 /compact:

时刻 事件 备注
0.000s SessionStart source=resume
0.028s PreCompact trigger=manual
14.928s SubagentStop agent_type=""(压缩是一个匿名子代理干的)
14.954s SessionStart source=compact

压缩会额外放出一次 SubagentStop 和一次 SessionStart。 如果你的 SessionStart hook 会做「注入项目上下文」之类的重活,它在一次长会话里会被跑很多遍,不是一遍。

Notification 本机未触发。 两次尝试都没拿到:一次让模型执行需要授权的命令(5 个事件,无 Notification),一次让模型空转等待(3 个事件,无 Notification)。原因很明确——claude -p 是 headless 的,没有人可以被通知,CLAUDE_CODE_SESSION_ATTENDED 这个环境变量本身就在说明这件事。以下是推测,未验证:Notification 大概率只在交互式 TTY 会话里、等待用户授权或长时间空闲时触发;想测它可能需要真开一个终端会话手动操作。我没有在 headless 下复现出来,就不编数字。

实测二:hook 真正收到的 stdin 字段

下面这张表是从落盘的 events.jsonl 里逐条读出来的,不是抄文档。所有事件共有 session_id / transcript_path / cwd / hook_event_name 四个字段,差异部分:

事件 独有字段(2.1.270 实测)
SessionStart source(startup / resume / compact)
UserPromptSubmit prompt_id、permission_mode、prompt
PreToolUse prompt_id、permission_mode、tool_name、tool_input、tool_use_id
PostToolUse 同上再加 tool_response(Read 工具是 {type, file:{filePath, content, numLines, totalLines}})
Stop stop_hook_active、last_assistant_message、background_tasks、session_crons
SubagentStop agent_id、agent_type、agent_transcript_path、stop_hook_active、last_assistant_message
PreCompact trigger(manual / auto)、custom_instructions
Notification 本机未触发,无实测字段

环境变量那一栏,原文写错了。hook 进程里以 CLAUDE 开头的变量实测是这些:

CLAUDECODE, CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR, CLAUDE_CODE_ATTRIBUTION_HEADER,
CLAUDE_CODE_CHILD_SESSION, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC, CLAUDE_CODE_ENTRYPOINT,
CLAUDE_CODE_EXECPATH, CLAUDE_CODE_MAX_OUTPUT_TOKENS, CLAUDE_CODE_MESSAGING_SOCKET,
CLAUDE_CODE_MESSAGING_TOKEN, CLAUDE_CODE_SESSION_ATTENDED, CLAUDE_CODE_SESSION_ID,
CLAUDE_CODE_USE_VERTEX, CLAUDE_CONFIG_DIR, CLAUDE_EFFORT, CLAUDE_PID, CLAUDE_PROJECT_DIR

CLAUDE_FILE_PATH 和 CLAUDE_TOOL_INPUT 都是 UNSET。正确的写法是从 stdin 的 JSON 里取:

# ❌ 原文写法:2.1.270 上这两个变量是空的,prettier 会去格式化一个空路径
npx prettier --write $CLAUDE_FILE_PATH

# ✅ 实测可用:从 stdin JSON 取 tool_input.file_path
payload=$(cat)
f=$(printf '%s' "$payload" | jq -r '.tool_input.file_path // empty')
[ -n "$f" ] && npx prettier --write "$f"

另外 CLAUDE_PROJECT_DIR 实测等于启动 claude 的那个 cwd,不是 git 仓库根目录。如果你在子目录里起会话,用它拼脚本路径会拼歪。

实测三:exit code 到底怎么解释

这是原文错得最要命的一处。原文说「非零状态退出,对应的工具调用将被阻止」——实测不是。

hook exit code 语义实测,双向标记文件确认

每个退出码一次独立会话,hook 写 hook-ran.txt、Bash 工具写 tool-ran.txt:

配置 tool-ran.txt hook 触发次数 turns 花费 模型看到什么
PreToolUse exit 0 YES 1 2 $0.0695 正常结果
PreToolUse exit 1 YES 1 2 $0.0405 正常结果,stderr 没进上下文
PreToolUse exit 2 NO 2 3 $0.0496 tool_result 带 is_error=true
PreToolUse exit 3 YES 1 2 $0.0409 正常结果
UserPromptSubmit exit 1 YES 1 2 $0.0409 正常结果
UserPromptSubmit exit 2 NO 1 0 $0 整轮中止,一次模型调用都没发生
Stop exit 2 — 3 3 $0.0797 被迫继续,多跑了 2 轮

只有 exit 2 有拦截语义。 exit 1 和 exit 3 在 PreToolUse 上完全等价于放行,hook 的 stderr 连模型的上下文都进不去——你以为自己拦住了危险命令,实际上它照跑。

exit 2 的证据不止标记文件。翻会话 transcript,能看到两条真实的 tool_result:

is_error=True | PreToolUse:Bash hook error: [.../exitcode.sh PreToolUse-exit2 2 0]:
               HOOKSTDERR-PreToolUse-exit2-fire1
is_error=True | PreToolUse:Bash hook error: [.../exitcode.sh PreToolUse-exit2 2 0]:
               HOOKSTDERR-PreToolUse-exit2-fire2     ← 模型重试了一次,又被挡回

两个细节:hook 的 stderr 原样进了模型上下文(所以拦截理由要写给模型看,写人话);模型被拦一次会重试,所以 hook 触发了 2 次而不是 1 次——写幂等,别在拦截分支里记账或发通知。

UserPromptSubmit exit 2 那一行的数字最好看:{"wall_s": 0.195, "duration_ms": 31, "num_turns": 0, "total_cost_usd": 0}。在模型被调用之前就中止了,31 毫秒,不花钱。 做输入侧合规检查(禁止提交含密钥的 prompt 之类)应该挂这里,而不是挂 PreToolUse。

Stop exit 2 会把模型拽回来继续干活。我的脚本里带了 max-fires=2 的保险,第 3 次触发改成 exit 0 才停下——不带保险就是死循环。stdin JSON 里的 stop_hook_active 就是给你判断这个用的,第二次进来时它会变成 true,别像我一样用计数文件硬扛。

实测四:matcher 十种写法,谁命中

同一个 PreToolUse 下面并排挂 10 条 matcher,一次会话里 Read / Write / Bash 各调一次,看谁被触发:

matcher 十种写法命中实测

matcher 写法 实际命中
"*" Read, Write, Bash
省略 matcher 字段 Read, Write, Bash
""(空串) Read, Write, Bash
"Bash" Bash
"Edit|Write" Write
"^Read$" Read
"Read,Write" Read, Write
"Rea"(前缀) 未触发
"bash"(小写) 未触发
"Notebook.*" 未触发(本次会话没调 Notebook 工具)

三条结论:

  1. "*" / "" / 省略,三者实测等价,都是全匹配。
  2. 匹配是「整名」的,不是「包含」——"Rea" 匹配不到 Read,所以 "^Read$" 的锚点其实可以省。正则能力是真的(| 生效),但别指望子串。
  3. 大小写敏感,"bash" 匹配不到 Bash。

另外在 Stop 事件上写 "matcher": "Bash"——照样触发。非工具类事件的 matcher 被直接忽略,写了等于没写,不会报错也不会过滤。

最后是配置格式。原文(和网上很多教程)用的单数 "hook": {…}:

hook 触发次数: 0    工具照常执行: YES    stderr: (空,没有任何报错)

2.1.270 只认复数数组 "hooks": [{…}]。 写成单数既不报错也不生效,这就是文章开头说的「最难查的那种失败」。正确写法:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "/abs/path/check.sh", "timeout": 5 }
        ]
      }
    ]
  }
}

实测五:挂 hook 要付多少延迟

同一个任务(Read + Write + Bash,一次会话共 9 次 hook 触发)× 3 种配置 × 3 轮,取中位数:

hook 延迟成本、并行与超时 fail-open

配置 3 轮 wall_s 中位数 API duration_ms 中位数
无 hook 9.577 / 10.267 / 11.553 10.267s 10.093s
空 hook(exit 0) 9.014 / 9.540 / 10.590 9.540s 9.355s
sleep 0.5 hook 13.831 / 13.871 / 16.742 13.871s 13.007s

空 hook 的中位数比无 hook 还低 0.7 秒——这不是说挂 hook 更快,是说 9 次空 hook 的开销完全落在模型响应时间的噪声带里,量不出来。3 轮的极差本身就有 2 秒。真正能量出来的只有 sleep 0.5 那组:9 次 × 0.5s = 4.5s 的理论开销,实际把中位数推高了 3.6s。

结论是别怕挂 hook,要怕 hook 里干慢活。 一个 jq 判断、一句 grep,在 3 秒一次的模型思考面前不值一提;一个 npx eslint 冷启动(实测 1~2 秒)乘以每次 Edit,就很可观了。

同一个事件挂两条 hook 时,它们是并行的:

A start 1789784312.065773000
B start 1789784312.065783000
A end   1789784313.229337000
B end   1789784313.229344000

A、B 启动相差 0.010 毫秒,各自跑了 1.164 秒——两条各 sleep 1 的 hook 总共只花 1.164 秒,不是 2 秒。

超时行为是 fail-open。配置 "timeout": 2、hook 里 sleep 10:

SLOW start 1789784541.…       ← 只有 start,没有 end 行,进程被杀了
POST start 1789784543.…       ← 2.143s 后 PostToolUse 就触发了

tool-ran.txt 存在 —— 超时的 PreToolUse 不拦工具,直接放行。 如果你把安全检查写成一个可能变慢的脚本(比如要联网查策略),它慢的那天就是它失效的那天。

实践效果

一句话版的实测结论,可以直接拿去改配置:

  • 配置键是 "hooks": [...](复数数组),单数 "hook": {...} 静默失效。
  • 想拦截,退出码只能是 2;1 和 3 都等于放行,stderr 也不会进模型上下文。
  • stderr 是写给模型看的,要写成可执行的指令(「用 X 替代 Y」),而不是 BLOCKED。
  • 被 exit 2 拦住后模型会重试,hook 要幂等。
  • 取文件路径用 jq -r '.tool_input.file_path',别用 $CLAUDE_FILE_PATH(不存在)。
  • 输入侧检查挂 UserPromptSubmit:exit 2 时 31ms 中止、0 turns、$0,比在 PreToolUse 上拦便宜一个量级。
  • Stop hook 用 exit 2 强制继续时,必须读 stop_hook_active 兜底,否则死循环。
  • matcher 大小写敏感、整名匹配、"*"/""/省略三者等价;非工具事件的 matcher 会被忽略。
  • 子代理工具名是 Agent;UserPromptSubmit 会收到机器生成的 <task-notification> prompt。
  • 别怕挂 hook(空 hook 的开销量不出来),要怕 hook 里跑慢活;超时是 fail-open,安全检查别写成可能变慢的脚本。

全部读数来自 38 次 claude -p 会话,总花费 $1.5248,原始 JSONL 与 settings 都留在本地实验目录。

踩坑

坑一:"hook" 与 "hooks" 一字之差,静默失效。 现象:按老教程写的配置一点反应都没有,hook 触发 0 次,工具照常执行,stderr 全空。根因:2.1.270 的 schema 是 {"matcher": ..., "hooks": [ ... ]},单数 "hook" 是未知键,被忽略且不校验。解法:改成复数数组;调试任何「hook 没生效」的问题,第一步先挂一个只写日志的 exit 0 hook 确认触发链路通不通,再调逻辑。

坑二:--settings 只认绝对路径。 现象:--settings lab/matrix.settings.json 报 Error: Settings file not found,但文件明明在。根因:相对路径是按会话的运行 cwd 解析的,我每次实验都切到独立工作目录,路径自然对不上。解法:--settings 和 hook 的 command 一律写绝对路径。

坑三:headless 下不喂 stdin,每次会话白等 3 秒。 现象:claude -p 会打印 Warning: no stdin data received in 3s,然后才开始干活。做延迟实验时这 3 秒直接毁掉全部读数。解法:claude -p "..." < /dev/null。这条对所有脚本化调用都适用,不只是做实验。

坑四:PostToolUse hook 改了文件,模型会收到一条系统提醒。 这是意外发现。让 PostToolUse hook 在模型 Write 完之后往文件里追加内容(模拟 formatter),transcript 里会多出一条 hook_additional_context 附件:

PostToolUse hook modified …/note.txt after your edit (likely a formatter). Your next Edit will not fail with a stale-file error, but if its old_string targets a region the hook reformatted, Read the file first.

也就是说 Claude Code 知道 formatter 类 hook 的存在并主动给模型打了招呼。好消息是自动格式化不会把模型的后续 Edit 搞崩;代价是每次这样的 hook 都会往上下文里塞一段话。

相关文章

Claude Code 禁止 rm -rf 怎么配:deny 规则两周拦下 14 次,实测还有 7 种写法照样能删,一个 hook 补齐

为了防 Claude Code 误删文件,我在全局 settings 里加了一条 deny 规则 Bash(rm -rf *)。翻本机 10 月 7 日到 21 日的会话记录:这条规则拦下 14 次删除,模型被拒后 8 次换新目录名、6 次直接放弃删除,一次都没换写法绕过。但在 Claude Code 2.1.285 上用 stub 逐字回放 22 种删除写法后发现:rm -fr、rm -r -f、rm -Rf、rm -r 在手动、auto、bypass 三种模式下都照删不误,/bin/rm、find -delete、bash -c 在手动和 bypass 模式下也能删。auto 模式下,工作目录内的 rm -fr 会被当成「acceptEdits 也会放行」的操作,连分类器都不过。另一道闸是内置的 dangerous-rm 检查,目标是 $D/* 这类变量展开时触发,bypass 模式也拦;多行命令里前面有 heredoc,它就跟不上同一条命令里的变量赋值。文末给出一个 PreToolUse hook,三种模式下补齐全部递归删除写法,并告诉模型被拒后该怎么做。

claude-codehooks+5
pitfalls2026年10月21日7 min
208

Claude Code 接 DeepSeek / GLM / Kimi 开 auto 模式:temporarily unavailable 括号里的原因怎么看,哪些重试没用(故障注入实测)

用第三方模型跑 Claude Code auto 模式,每个有副作用的动作都会用你的会话模型向同一个后端多发一次判定请求:系统提示词 132,470 字符,单次约 2.97 万输入 token,比主循环那一步还多。判定请求一失败,就出现「xxx is temporarily unavailable, so auto mode cannot determine the safety of Bash」。本文用故障注入代理把每种失败对应的报错原文测了一遍:500 → (server error)、429 → (rate-limited)、529 → (overloaded)、断连 → (connection failed),这几种会自动重试 5 次;超过 60 秒没回 → (timed out);**没有括号 = 后端拒收(4xx),只试 1 次,重试多少遍都一样**;后端回了但模型没给判定 → could not evaluate,连续 3 次就退回人工确认。另外实测 CLAUDE_CODE_AUTO_MODE_MODEL 不生效,判定器只能跟着会话模型换。Claude Code 2.1.285,真实后端 DeepSeek v4-pro 与小米 MiMo v2.5。

claude-codedeepseek+7
pitfalls2026年10月11日11 min
643

Claude Code 上下文太长怎么办:Prompt is too long 会自动压缩,中转站 / DeepSeek 的 maximum context length 却不会(实测)

Claude Code 靠报错文案判断「上下文太长」:后端说 prompt is too long 或 input is too long for requested model,它会自动压缩对话后重发,用户无感;DeepSeek 和 OpenAI 风格中转返回的 This model's maximum context length is …,它不认识,直接报 API Error: 400,每轮都失败。手动 /compact 有效;更好的办法是用 CLAUDE_CODE_MAX_CONTEXT_TOKENS(非 claude- 模型 ID)或 CLAUDE_CODE_AUTO_COMPACT_WINDOW(claude- 模型 ID)告诉它真实上限,让它提前压缩。Claude Code 2.1.285 + 本地 stub 实测。

claude-codedeepseek+6
pitfalls2026年10月5日8 min
726

Claude Code 一直卡住、转圈没反应怎么办:后端不回时它要等 6 分钟,重试满 10 次可能卡一个多小时(实测)

Claude Code 转圈不动、或者停在 Retrying in 0s,多半是后端没有回数据。实测 2.1.285(API key + ANTHROPIC_BASE_URL):后端不回响应头时每次等 6 分钟(360 秒)才判超时,把 API_TIMEOUT_MS 调到 60 万、90 万也不变,只能调小;默认重试 10 次,总共可能卡一个多小时。中转站如果把回复缓冲到生成完才返回,超过 6 分钟的回复永远拿不到。按 Esc 可以随时中断。全部在本地 stub 上实测。

claude-code中转站+6
pitfalls2026年10月5日10 min
649