你给 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.json的statusLine.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 ps、kubectl)卡住整条栏。 - 忘了处理
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 设为空字符串。重启会话即可。