在实现 #5790(PR #5879)时对新钩子做超出自测矩阵的模糊测试顺带测出来的性能特性,不是功能缺陷,当前没有任何 agent 会踩到,故按观察项记录、不进队列。未做任何改动。
现象
.claude/hooks/guard-main-checkout-bash.sh 的 split_segments() 与 tokenize() 都是纯 bash 的逐字符循环(for ((i=0;i<n;i++)); ch="${s:i:1}"),配合 tok+="$ch" 的字符串累加,整体随命令长度超线性增长。实测(fixture 为临时 git 仓库,命令形状 tee 加一个超长单 token):
| 命令长度 |
钩子耗时 |
| 200 |
46 ms |
| 1000 |
78 ms |
| 2000 |
126 ms |
| 4000 |
204 ms |
| 8000 |
478 ms |
| 16000 |
1565 ms |
| 32000 |
5422 ms |
| 65536 |
超过 20s(模糊测试里以 timeout 20 计,rc=124) |
为什么现在不影响任何人
- 现实命令形状是 18–20 ms,与长度无关地快:
pnpm --filter @objectstack/spec test(36 字符)18 ms、一条 161 字符的 flock ... -c "NODE_OPTIONS=... pnpm ... test --maxWorkers=2" 20 ms、带 --format 与多路径的 git log(102 字符)19 ms。
- 大 heredoc 不受影响:正文在进入字符循环之前就被
strip_heredocs() 整段剥离,所以「用 heredoc 写一个大文件」这个最可能产生超长命令的真实场景很便宜 —— 实测 25938 字符的 heredoc 命令仅 50 ms,且判定仍然正确(引入行自己的重定向照样拦)。
- 要触发到秒级,需要单条命令里出现一个万字符量级的 token(不是长命令,而是长单词)。当前仓内没有这种调用形状。
同源影响
PR #5879 是从 objectui 逐例移植的成品(objectstack-ai/objectui#3452),两边可执行代码逐字节相同,所以 objectstack-ai/objectui 的同名钩子有完全相同的特性。修的话应两仓一起,否则会引入本来刻意避免的守卫漂移。
若将来要修
思路(未验证,留给接手的人判断):把逐字符循环换成一次性的 read -r -a 加引号预处理,或用 ${s//[^chars]/} 之类的批量替换替代逐字符累加;也可以直接给一个长度上限 —— 超过某个阈值就 fail open(与钩子既有的「解析不了一律放行」哲学一致,而且比让每个 Bash 调用等 5 秒更符合「guard 是防呆不是安防」)。后者改动最小。
复现
# 在 PR #5879 的分支上
tmp=$(mktemp -d); mkdir -p $tmp/m/pkg
( cd $tmp/m && git init -q . && git config user.email a@b.c && git config user.name a && : > pkg/x.ts && git add -A && git commit -qm i )
arg=$(head -c 32000 /dev/zero | tr '\0' 'a')
time jq -nc --arg c "tee $arg.ts" --arg w "$tmp/m" '{cwd:$w,tool_name:"Bash",tool_input:{command:$c}}' \
| .claude/hooks/guard-main-checkout-bash.sh
按关键词(hook / latency / tokenize / guard-main-checkout / PreToolUse)搜过 open issue,未搜到同类记录。
在实现 #5790(PR #5879)时对新钩子做超出自测矩阵的模糊测试顺带测出来的性能特性,不是功能缺陷,当前没有任何 agent 会踩到,故按观察项记录、不进队列。未做任何改动。
现象
.claude/hooks/guard-main-checkout-bash.sh的split_segments()与tokenize()都是纯 bash 的逐字符循环(for ((i=0;i<n;i++)); ch="${s:i:1}"),配合tok+="$ch"的字符串累加,整体随命令长度超线性增长。实测(fixture 为临时 git 仓库,命令形状tee加一个超长单 token):timeout 20计,rc=124)为什么现在不影响任何人
pnpm --filter @objectstack/spec test(36 字符)18 ms、一条 161 字符的flock ... -c "NODE_OPTIONS=... pnpm ... test --maxWorkers=2"20 ms、带--format与多路径的git log(102 字符)19 ms。strip_heredocs()整段剥离,所以「用 heredoc 写一个大文件」这个最可能产生超长命令的真实场景很便宜 —— 实测 25938 字符的 heredoc 命令仅 50 ms,且判定仍然正确(引入行自己的重定向照样拦)。同源影响
PR #5879 是从 objectui 逐例移植的成品(objectstack-ai/objectui#3452),两边可执行代码逐字节相同,所以
objectstack-ai/objectui的同名钩子有完全相同的特性。修的话应两仓一起,否则会引入本来刻意避免的守卫漂移。若将来要修
思路(未验证,留给接手的人判断):把逐字符循环换成一次性的
read -r -a加引号预处理,或用${s//[^chars]/}之类的批量替换替代逐字符累加;也可以直接给一个长度上限 —— 超过某个阈值就 fail open(与钩子既有的「解析不了一律放行」哲学一致,而且比让每个 Bash 调用等 5 秒更符合「guard 是防呆不是安防」)。后者改动最小。复现
按关键词(hook / latency / tokenize / guard-main-checkout / PreToolUse)搜过 open issue,未搜到同类记录。