chore(hooks): 拦截共享 refs/stash 的 git stash,worktree 隔离盖不住它 (#3430) - #3433
Merged
Conversation
refs/stash 存在共用的 .git 目录里,所有 linked worktree 共享同一个 LIFO 栈,
「一任务一 worktree」的物理隔离对它无效:两个 agent 在各自 worktree 里 push/pop
操作的是同一个栈,A 的 pop 取到 B 刚 push 的改动,A 自己的改动留在栈上被 B 取走。
pop 还会报成功,唯一现象是别人的文件出现在你的 git status 里。
新增 PreToolUse 钩子 guard-shared-stash.sh(Bash matcher,退出码契约与
guard-main-checkout.sh 一致:0 放行 / 2 拦截并把理由写到 stderr):
- 拦截 push/save/pop/drop/clear/branch 与裸 git stash;
- 放行只读与按 SHA 取回的形式:list / show / create,以及带字面十六进制
object id 的 apply / store —— stash@{N} 是共享栈里的**位置**,一律拦截;
- 分段时识别引号,写「关于禁令」的文档/grep 不会被禁令自己拦下;
- 解析不了的 payload 一律 fail open;逃生阀 OS_ALLOW_STASH=1。
配套 guard-shared-stash.selftest.sh(32 条用例)与 CLAUDE.md 一节(禁令 +
三种替代法)。反向验证:把判定函数改成恒放行,15 条 block 用例全红、17 条
allow 用例不动。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
首词判定意味着 bash -c '…' / xargs / ssh 这类包装调用抓不到。这是刻意取舍: 目标是 agent 任务中途下意识敲的 git stash push,不是有意绕行的人(那有 OS_ALLOW_STASH=1)。改成在整条命令里做子串匹配的话,连 grep "git stash" 这个文件本身都会被拦。写进 header,省得下一个读的人自己去发现。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
yinlianghui
marked this pull request as ready for review
August 6, 2026 04:42
This was referenced Aug 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3430
问题
git stash的栈存在共用的.git目录里(refs/stash),所有 linked worktree 共享同一个 LIFO 栈。AGENTS.md 要求的「一任务一 worktree」物理隔离覆盖不到 stash:两个 agent 在各自 worktree 里 push/pop,操作的是同一个栈 —— A 的pop取到 B 刚 push 的改动,A 自己的改动留在栈上等着被 B 取走。2026-08-06 03:56Z 真实发生过一次:#3422 的反向验证
git stash push -- packages/fields/.../RecordPickerDialog.tsx之后git stash pop,Dropped 的却是另一个 agent 的b52e3aa(WIP onclaude/issue-5733-…,两个 plugin-detail 文件),双方在途改动完全换位。失败现象极具迷惑性:pop 显示成功,唯一线索是别人的文件出现在你的git status里;此时任一方git add -A,对方的改动就混进自己的 PR。guard-main-checkout.sh拦不住它。改动
1. 新钩子
.claude/hooks/guard-shared-stash.sh—— PreToolUse /Bashmatcher,退出码契约与既有guard-main-checkout.sh完全一致(0 放行,2 拦截并把理由写 stderr),同样先查逃生阀、同样 jq 优先 + 无 jq 时 sed 兜底解析 payload。拦截:裸
git stash、push/save/pop/drop/clear/branch。放行:
git stash list/show—— 只读,不动栈;git stash create—— 只生成 commit 对象并打印 object id,不写入 ref(git-stash(1)),正是「按 SHA 取回」工作流的安全原语;git stash apply SHA/store SHA—— 事故当天的补救路径,但只认字面十六进制 object id;stash@{N}是共享栈里的位置,轮到你执行时可能已经是别人的条目,一律拦截。两个刻意的设计点:
grep -n "cd x && git stash pop" AGENTS.md第一个词是grep,不会被拦 —— 写「关于禁令」的文档和 grep 不该被禁令自己拦下(objectstack#4890 的教训:写规则的那个 PR 恰好违反了自己写的规则)。逃生阀
OS_ALLOW_STASH=1。2.
.claude/hooks/guard-shared-stash.selftest.sh—— 32 条用例,构造与 Claude Code 相同的 PreToolUse payload 喂给钩子断言 block/allow,不需要 install/build/网络。3.
CLAUDE.md一节 —— 禁令、为什么(共享refs/stash)、issue 里的三种替代法(patch 文件来回 apply / 临时 commit / 第二个 worktree)。验证
.claude/hooks/guard-shared-stash.selftest.sh→32 passed, 0 failed(覆盖:各种 mutating 形式、stash@{N}位置引用、经&&/$( )/git -C到达的形式、只读与 SHA 形式、无关命令、写文档提到git stash、逃生阀、空 payload、无 jq 兜底路径)。反向验证(先定方向再跑):把
check_segment改成恒返回 0(放行),预期只有 15 条 block 用例转红、17 条 allow 用例不动 —— 实测正是17 passed, 15 failed,证明矩阵不是空过的。node scripts/check-control-bytes.mjs绿(3630 tracked files);改动文件另做了一次超出该 gate 扫描面的grep -naP自查,干净。说明
changeset-guard.yml也只在.changeset/**变更时触发)。Generated by Claude Code