Claude Code 自定义状态栏脚本报错或卡顿 —— 排查与修复

Claude Code 状态栏报错、空白或让整个提示卡顿 —— 自定义脚本在静默失败。从退出码、输出格式、超时限制三方面定位。

你给 Claude Code 接了一个自定义状态栏脚本,显示 git 分支、模型名、token 预算。第一次跑很好。然后就坏了:栏里出现 statusline: error 这种字面文本、或者干脆空白,更糟的是每轮回复前整个提示都要卡几秒,因为脚本在阻塞。Claude Code 的状态栏脚本每次刷新都会跑一次 —— 通常每轮一次 —— 任何非零退出、缓慢命令或畸形 stdout 都会拖累体验。绝大多数修复归根结底就是让脚本变快、变确定、stderr 静默。

常见原因

按频率排序。

1. 脚本以非零退出

状态栏运行器期望退出码 0。如果你 set -e 然后在非 git 目录跑 git rev-parse --abbrev-ref HEAD,脚本会以 128 退出,栏里就报错了。

怎么判断:手动跑完脚本 echo $? 是非零;或者 claude --debug 日志里有 statusline exited 128

2. 脚本太慢(阻塞提示)

状态栏脚本有软超时(常见 1-2 秒)。curl 远端 API、git fetch、递归 find 都很容易超。脚本要么被杀,要么提示明显卡住。

怎么判断:time ./statusline.sh 超过 500ms;或者你能感到每轮前提示有停顿。

3. 输出含 ANSI / 控制字符被混乱渲染

直接输出 ANSI 转义序列(颜色码),有的渲染器认,有的不认。嵌入换行会被拆成两行,Tab 变成字面 \t

怎么判断:状态栏里出现字面 \033[31m,或本该一行的地方断行了。

4. 路径或解释器不匹配

#!/usr/bin/env bash 是可移植的;#!/bin/bash 在 Alpine 上不一定存在。#!/usr/bin/python 在 macOS 可能解析到 Python 2。shebang 错了脚本根本跑不起来。

怎么判断:head -1 statusline.sh 显示的 shebang 跟系统上 which bash / which python3 对不上。

5. 脚本往 stderr 写东西,噪声漏进栏里

有的渲染器把 stderr 跟 stdout 拼起来。git status 这种往 stderr 打警告的命令,会让可见状态栏里出现奇怪文字。

怎么判断:栏里出现 warning: ... 之类的进度提示。

6. settings.json 指向不存在或不可执行的文件

statusLine.command 引用的路径不存在,或文件存在但没 +x。脚本根本不会被调用。

怎么判断:ls -l <path> 显示文件缺失或没执行位;claude --debug 显示 statusline: no such file

7. JSON 模式状态栏返回非法 JSON

如果你配的是 JSON 模式(返回 {"text": "...", "color": "..."}),多一个逗号或字段名错就解析失败,回退到空。

怎么判断:脚本 stdout 看着挺好,栏却是空。claude --debug 显示 statusline: json parse error

开始前

  • 记录栏是”空”、“错”还是”内容错”。
  • 看问题是持续的还是间歇的(间歇通常意味着脚本里有网络调用)。
  • ~/.claude/settings.jsonstatusLine.command 找到脚本路径。
  • 留一份”应该长这样”的截图或文本作为对照。

需要收集的信息

  • 状态栏脚本内容。
  • ~/.claude/settings.json(或项目 settings)里的 statusLine 块。
  • time ./statusline.sh 的耗时。
  • ./statusline.sh 1>/tmp/sl.out 2>/tmp/sl.err; echo exit=$? 的输出。
  • Claude Code 版本。

一步步修复

先手动调用 —— 单独跑不起来,在提示里也绝对跑不起来。

第 1 步:直接跑脚本,看输出

time ./statusline.sh
echo "---"
./statusline.sh 1>/tmp/sl.out 2>/tmp/sl.err
echo "exit=$?"
cat /tmp/sl.out
echo "---stderr---"
cat /tmp/sl.err

三项检查:退出 0 吗?stdout 干净吗?stderr 安静吗?有一项不过,问题在脚本里,不在 Claude。

第 2 步:让脚本对边缘情况容错

把可能失败的命令包上错误抑制:

#!/usr/bin/env bash
set +e
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "no-git")
model="${CLAUDE_MODEL:-unknown}"
printf "%s | %s" "$branch" "$model"
exit 0

注意显式的 exit 0 —— 防止最后一条命令失败带挂整个脚本。

第 3 步:在脚本内部强制设短超时

对可能阻塞的命令:

branch=$(timeout 0.3s git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "?")

状态栏里绝对不要同步调网络 API。要缓存:

CACHE=/tmp/claude-statusline-cache
if [ ! -f "$CACHE" ] || [ "$(find "$CACHE" -mmin +5)" ]; then
  curl -s --max-time 1 https://api.example.com/quota > "$CACHE.tmp" && mv "$CACHE.tmp" "$CACHE"
fi
quota=$(cat "$CACHE" 2>/dev/null || echo "?")

第 4 步:剥掉 ANSI 和换行

printf "%s | %s" "$branch" "$model" | tr -d '\n\r\t'

如果你想加颜色而且渲染器认,先用一个字面转义测一下。否则别上色,让 Claude Code 自己给栏上样式。

第 5 步:在脚本边界吞掉 stderr

在 settings.json 里包一层:

{
  "statusLine": {
    "command": "/bin/sh -c '/Users/me/.claude/statusline.sh 2>/dev/null'"
  }
}

这样 stderr 永远到不了渲染器。

第 6 步:修权限和 shebang

chmod +x ~/.claude/statusline.sh
head -1 ~/.claude/statusline.sh   # 应当是 #!/usr/bin/env bash

用解释器绝对路径测:

/usr/bin/env bash ~/.claude/statusline.sh

如果这样能跑、直接调脚本不行,问题就在 shebang。

第 7 步:校验 JSON 模式输出(如使用)

./statusline.sh | jq .

必须无错解析。一份正确的 JSON 模式响应:

{"text": "main | opus-4.7 | 12k tokens", "color": "cyan"}

如果你的 settings 用了 type: "json"(或等价配置),解析不了的输出就给你一个空栏。

验证

  • 重启 Claude Code,栏应在会话启动 200ms 内填满。
  • 进一个不是 git 仓库的目录,确认栏仍能工作(走兜底路径)。
  • 断网,确认栏仍能工作(走缓存路径)。
  • 在好几个不同 cwd 下 time ./statusline.sh;全部应低于 500ms。

长期预防

  • 状态栏脚本控制在 50 行以内。再大就该独立成单独工具了。
  • 绝不同步调网络或 git-fetch;后台刷新写缓存即可。
  • 结尾防御性写 exit 0
  • /tmp(无 git、无 node_modules)下测脚本 —— 它在那儿不应挂掉。
  • 脚本进 dotfiles 版本管理,跟生产脚本一个标准对待。
  • CI 加冒烟测试:跑脚本、断言退出 0、输出小于 200 字符、耗时低于 500ms。
  • 栏要有用但克制 —— 模型名、git 分支,顶多一项自定义信号。塞太满每轮都被拖慢。

常见坑

  • git status(递归、慢)而不是 git rev-parse(一次系统调用)。
  • 加了”token 计数”,每次刷新都重新分词整个上下文。
  • 脚本路径硬编码 ~;Claude 在不同启动上下文里 ~/.claude/settings.json 解析方式不一样。
  • 让某个慢的第三方命令(docker pskubectl)卡住整条栏。
  • 忘了处理 CLAUDE_MODEL 环境变量未设的情况 —— 相关的环境注入问题见 Claude Code settings.json 未加载
  • 把花括号写进输出串 —— 部分渲染器会解释它们,改用 []()

FAQ

Q: 手动跑脚本好用,在 Claude 里却空白,为什么?

最常见是环境差异。Claude 启动脚本时没有你交互式 shell 的 PATH 和环境变量。脚本里用绝对路径,只引用 Claude 提供的环境($CLAUDE_MODEL$CLAUDE_SESSION_ID)。

Q: 输出最长能写多长?

软上限大约 200 字符。超了渲染器会截断或换行。保持紧凑。

Q: 状态栏能调工具吗?

不能。它就是一条 shell 命令,在 agent loop 之外跑,拿不到代理状态。要动态数据,写一个后台守护进程往缓存文件写,然后状态栏读这个文件。

Q: 为什么会话启动时状态栏会跑两次?

一次会话 init,一次第一轮渲染后。脚本要幂等而且廉价 —— 相关的阻塞调用问题见 Claude Code 工具执行卡死

Q: 能完全关掉状态栏吗?

从 settings.json 里删掉 statusLine 键,或把 statusLine.command 设为空字符串。重启会话即可。

标签: #Claude Code #statusline #排查 #configuration #scripting