跨 Shell 参数拆分导致 AI 误删仓库
一则 Claude Code 问题报告称,代理生成的辅助脚本因 zsh 与 bash 参数拆分行为不同,加上删除路径未加引号,最终删除了包含 .git 的仓库。报告提供最小复现,事故发生于跳过权限确认的模式下。
AI 深度解读
Claude Code 自己写的构建辅助脚本,最终删掉了整个 Git 仓库,连 .git 也没留下。按报告者的复现,事故来自一次跨 Shell 的参数传递:zsh 没按预期拆开的字符串,到了 bash 里又被拆开,删除范围因此扩大。
这起案例值得程序员读几分钟,因为危险不只在 rm -rf。生成脚本、拼接参数、调用另一个 Shell,每一步单看都很常见,组合起来却可能让清理构建产物变成删除源码。不过,目前只有报告者的描述和最小复现,还没有 Anthropic 确认的根因或修复。
一段构建脚本怎么删到了仓库根目录
报告者在 macOS 上以 bypassPermissions 模式运行 Claude Code,让它编写辅助脚本,重新生成主工作树及两个变体的构建产物。
驱动循环通过下面这段代码准备参数,输入字符串类似 "main <repo-root> main",预期拆成三个参数:
set -- $side
要看的是 $side:它没有加引号,而调用者依赖 Shell 自动按空白拆分字符串。
据报告,这个 Bash 工具实际使用的登录 Shell 是 zsh。zsh 对这里未加引号变量的处理与 bash 不同,没有按作者预期拆分,因此辅助脚本收到的是一个完整字符串,而非三个参数。
辅助脚本随后在自己的 bash 调用中拼接路径并执行清理:
D=$(dirname $R)/$N; rm -rf $D
危险集中在 rm -rf $D。bash 会对未加引号的 $D 进行单词拆分,结果让删除目标越过预期的输出目录,触及真实仓库根目录。
前一层没有发生的拆分,在后一层发生了,最终改变了删除命令收到的参数。
原文没有给出完整辅助脚本和所有变量的取值,因此不能据此补出完整的路径展开过程。但报告指出,仓库连同 .git 被删除,本地所有分支和提交随之丢失,之后执行 git status 返回 fatal: not a git repository。
为什么这起事故被评为 high,而非 critical
报告者于 2026-11-04 提交 GitHub issue,并提供了可在 /tmp 运行的最小复现。按其描述,SSD 的 TRIM 支持使删除后的数据无法恢复。
原文给出的严重性是 7/high,评分分项为:
scope 2:影响一个本地仓库,没有提到生产环境或第三方受损。recoverability 3:报告称 TRIM 导致数据无法恢复。realisation 2:删除已经实际发生。impact 0:原文没有进一步解释这一项的评分口径。
这里的 high 不能理解为损失轻。对没有其他副本的本地开发成果来说,删除 .git 会同时失去代码和本地版本历史。只是现有报告没有建立更广泛的影响范围。
证据边界也要保留:截至提交时,issue 只有 bug、data-loss、has repro、area:bash、area:permissions、platform:macos 等自动标签,没有维护者回应。报告者给出了第三方缓解 hook,但它不是官方修复。这个问题究竟普遍影响以 zsh 为登录 Shell 的配置,还是依赖更具体的环境条件,原文没有说清。
我的判断
我认同原文强调的风险链条:AI 既写脚本又执行脚本,执行前没有人工检查,路径变量未加引号的问题便直接进入了破坏性操作。
但原文认为代理「无从知道」参数拆分假设不成立,这个说法偏绝对。实际 Shell、参数数量、最终删除目标,都可以成为执行前的检查对象。现有材料只能说明这次没有正确处理这些条件,不能证明它们无法检查。
与直接依赖字符串拆分相比,明确传递参数、核对参数数量,更容易发现接口错位;给路径变量加引号,则能避免它在删除命令中再次被拆分。两者解决的是不同环节的问题。只补最后一个引号,也不能证明拼出来的路径就是正确目标。
这些是根据故障链条提出的工程判断,原文没有提供修复方案之间的对照测试,也没有证明第三方 hook 能覆盖同类事故。bypassPermissions 是本次事故的运行条件,但单凭这份报告,不能断言换一种权限模式就一定能阻止删除。
能拿来做什么
- 让 AI 编写前端构建清理脚本时,要求它明确列出允许删除的产物目录,并在执行前展示最终路径;不要只看变量名像不像输出目录。
- 审查跨 zsh、bash 调用的脚本时,重点检查参数如何传入、实际收到几个参数,以及路径变量是否加引号。不要把不同 Shell 的拆分规则当成同一套。
- 在
/tmp的独立样例中验证清理逻辑,先打印命令将收到的每个参数,再测试删除行为。复现应覆盖主工作树和变体目录的传参方式。 - 把尚未推送的提交也纳入备份考虑。本案连
.git一起被删,仓库内部的本地分支无法充当独立备份。