Skip to content

chore(hooks): 拦截共享 refs/stash 的 git stash,worktree 隔离盖不住它 (#3430) - #3433

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-3430-stash-guard
Aug 6, 2026
Merged

chore(hooks): 拦截共享 refs/stash 的 git stash,worktree 隔离盖不住它 (#3430)#3433
yinlianghui merged 2 commits into
mainfrom
claude/issue-3430-stash-guard

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

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 on claude/issue-5733-…,两个 plugin-detail 文件),双方在途改动完全换位。失败现象极具迷惑性:pop 显示成功,唯一线索是别人的文件出现在你的 git status 里;此时任一方 git add -A,对方的改动就混进自己的 PR。guard-main-checkout.sh 拦不住它。

改动

1. 新钩子 .claude/hooks/guard-shared-stash.sh —— PreToolUse / Bash matcher,退出码契约与既有 guard-main-checkout.sh 完全一致(0 放行,2 拦截并把理由写 stderr),同样先查逃生阀、同样 jq 优先 + 无 jq 时 sed 兜底解析 payload。

拦截:裸 git stashpush / 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 恰好违反了自己写的规则)。
  • 解析不了就 fail open:拦下自己看不懂的命令的守卫,最后一定会被关掉,那就一条也守不住。

逃生阀 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.sh32 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 自查,干净。

说明

  • 本仓只做工具侧强制;AGENTS.md 文字条款与 os-dev 定义那一半在 objectstack#5742(另一条 lane)。
  • 无 changeset:纯流程/工具改动,不发包、对用户不可见(changeset-guard.yml 也只在 .changeset/** 变更时触发)。

Generated by Claude Code

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
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 6, 2026 3:52am

Request Review

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Aug 6, 2026
首词判定意味着 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
yinlianghui marked this pull request as ready for review August 6, 2026 04:42
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 03b0f70 Aug 6, 2026
15 of 16 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3430-stash-guard branch August 6, 2026 04:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[process] git worktree 共享 refs/stash:并行 agent 之间 stash push/pop 会互相顶掉在途改动

2 participants