Skip to content

fix(tooling): merge 驱动不再绑定到「上一个装过依赖的 worktree」,并补上悬空判红的断言 (#4868) - #4908

Merged
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-4868-merge-driver-worktree-independent
Aug 3, 2026
Merged

fix(tooling): merge 驱动不再绑定到「上一个装过依赖的 worktree」,并补上悬空判红的断言 (#4868)#4908
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-4868-merge-driver-worktree-independent

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes #4868

缺陷:遵守收尾纪律,就是触发缺陷的动作

scripts/setup-git-hooks.mjs绝对路径写进 merge.os-regen.driver。linked worktree 共用同一份 /home/user/objectstack/.git/config,于是每次有人 pnpm install,全容器的驱动就被改指向那个人的 worktree。

关键的一环不是花絮:AGENTS.md 要求任务收尾时 git worktree remove。所以「按规矩收尾」正是让驱动悬空的那个动作 —— 这就是它为什么必然反复发生,而不是偶发。本单实施期间实测路径漂过四轮(-4796-4793-4843-4890),其中 -4843-4890 是我开工到建 worktree 这几分钟内、被另一个并行 agent 的 install 改掉的。

驱动一坏,所有 agent 凡碰到 .gitattributesmerge=os-regen 映射的文件(spec-changes.jsonauthorable-surface.jsoncontent/docs/references/** 等)的 merge 全部 MODULE_NOT_FOUND。

比议题描述的更糟一点:实测驱动崩溃后,git 把该路径留成 UU,里面是 ours、且零冲突标记:

OURS_MARKER present: 1
THEIRS_MARKER present: 0
conflict markers: 0

也就是说一次条件反射式的 git add -A 就会静默吞掉 incoming 那一侧 —— 正是 #4616 那类「无冲突标记地静默吞掉别人的删除」。

为什么不能简单改回相对路径

原第 40 行的注释说明绝对路径是刻意的:躲开「相对路径解析到错的根」。所以解法必须同时满足两个约束:

  1. 不绑定到任何具体 worktree;
  2. git 调用驱动时能解析到当前工作树的根。

新值:

node "$(git rev-parse --show-toplevel)/scripts/git-merge-regen.mjs" %O %A %B %P

git 把 merge 驱动命令整条交给 shell,所以命令替换在每次调用时、在正在被合并的那个工作树里求值 —— 两个约束同时满足。既有 clone 下次 pnpm install 自愈。

议题建议的 ! 形式是错的 —— 实测,没有照抄

议题建议 !node "$(git rev-parse --show-toplevel)"/scripts/...。在 linked worktree 里实测(git 2.43):

    !node "$(git rev-parse --show-toplevel)/scripts/x.mjs" .merge_file_9Sb2gX ... 'art.json': 1: !node: not found
    Auto-merging art.json
    CONFLICT (content): Merge conflict in art.json

! 前缀是 alias / credential-helper 的语法;merge 驱动的值本来就直接交给 shell,所以 !node 是在找一个真名叫 !node 的程序。失败方式还是静默降级成文本合并 —— 比现状更隐蔽。已去掉 !

占位符必须保持不加引号 —— 也是实测

顺手「改进」引用会引入新缺陷。git 自己已经对 %P 做了 shell 引用:

不加引号: ARGV=[".merge_file_mNVhOu",".merge_file_HqfCyx",".merge_file_Zupv9J","art.json"]
自己加引号: ARGV=[".merge_file_mok9gZ",".merge_file_sKzy0x",".merge_file_wRJoN3","'art.json'"]

即自己加引号会把字面引号塞进 pathname。已在代码注释里记下这个坑。

其余验证:$(git rev-parse --show-toplevel) 形式在 linked worktree 里、从工作树根和子目录发起的 merge 都正确解析到该 worktree 根。

自检本身是反面教材,所以新增断言读实时 config

pnpm check:merge-driver 的每一条既有检查都自建临时仓库、注册自己的驱动,所以它和真实调用路径不是同一条。开工时的实测 —— 此时真实 config 指向已删除的 -4843:

$ git config --show-origin --get merge.os-regen.driver
file:.git/config  node "/home/user/objectstack-4843/scripts/git-merge-regen.mjs" %O %A %B %P
$ ls /home/user/objectstack-4843/scripts/git-merge-regen.mjs
ls: cannot access ...: No such file or directory

$ pnpm check:merge-driver
✓ merge driver wiring is consistent (6 path(s) deliberately excluded).
✓ check-regen-pending self-test passed.
=== EXIT: 0 ===

全绿。 这就是它能漂四轮没人发现的原因。

新增 registeredDriverResolves()实时 config,三种判红方式:

判红 1 —— 脚本不存在(悬空,即本单的现场)

✓ .githooks/pre-commit is executable in the index (100755)
✗ merge.os-regen.driver names a script that does not exist:
    /home/user/objectstack-4843/scripts/git-merge-regen.mjs
  Registered value: node "/home/user/objectstack-4843/scripts/git-merge-regen.mjs" %O %A %B %P
  Every merge touching a merge=os-regen path in this clone dies with MODULE_NOT_FOUND,
  and git leaves the path CONFLICTED with ours in it and no conflict markers.
  Fix: pnpm install  (re-registers the driver for this worktree)

✗ merge driver wiring is inconsistent — see above.
EXIT=1

判红 2 —— 指向本工作树以外(缺陷咬人一步)

这条是我加的、议题没提的:worktree 还在、脚本还能解析,但驱动已经绑在别人的 worktree 上 —— 对刚 install 的那个人是绿的,对其他所有人已经是坏的。等到它被删才报,已经晚了。

✗ merge.os-regen.driver points OUTSIDE this worktree:
    /home/user/objectstack-4719/scripts/git-merge-regen.mjs
  Linked worktrees share one .git/config, so this is bound to another worktree and
  breaks for everyone the moment that one is removed.
  Fix: pnpm install  (re-registers the driver for this worktree)
EXIT=1

判红 3 —— 与注册值漂移

实拍(我的 pnpm install 用旧代码写进 config 之后):

✗ merge.os-regen.driver has drifted from what setup-git-hooks.mjs registers.
    registered: node "/home/user/objectstack-4868/scripts/git-merge-regen.mjs" %O %A %B %P
    expected:   node "$(git rev-parse --show-toplevel)/scripts/git-merge-regen.mjs" %O %A %B %P
  Fix: pnpm install
EXIT=1

制造悬空态用的是进程级的 GIT_CONFIG_COUNT/GIT_CONFIG_KEY_0 注入,没有改共享的 .git/config —— 并行 agent 的 merge 不受影响。(判红 1 里既有的 endToEnd 也跟着红了,那是该注入方式会漏进它自建的临时仓库所致,不是真实场景的行为;判红 2 干净地只有新断言红。)

修好后:

✓ .gitattributes ↔ regen-artifacts.mjs agree on 7 path(s)
✓ all 14 gen:/check: names resolve in @objectstack/spec
✓ .githooks/pre-commit is executable in the index (100755)
✓ merge.os-regen.driver resolves in THIS worktree (scripts/git-merge-regen.mjs)
✓ end-to-end: conflicting packages/spec/spec-changes.json merged without markers and recorded as pending

✓ merge driver wiring is consistent (6 path(s) deliberately excluded).
✓ check-regen-pending self-test passed.
EXIT=0

真实合并路径验证(比 self-test 更重要 —— self-test 已经证明过它会骗人)

在本 worktree 里构造两侧都改 packages/spec/spec-changes.json 的真实合并,不带任何 -c 覆盖,驱动完全由共享 .git/config 解析:

$ git config --get merge.os-regen.driver
node "$(git rev-parse --show-toplevel)/scripts/git-merge-regen.mjs" %O %A %B %P

$ git merge lab3-theirs -m lab
  ⟳ packages/spec/spec-changes.json
     not text-merged — it is generated. Regenerate from the merged tree:
       pnpm --filter @objectstack/spec gen:spec-changes
     The pre-commit hook will not let this commit through until you do.
Auto-merging packages/spec/spec-changes.json
Merge made by the 'ort' strategy.

=== pending marker contents ===
packages/spec/spec-changes.json
=== conflicted paths: 0 ===

驱动被正确调用、pending marker 落盘、零冲突。同一场景在悬空值下则是本 PR 开头那段 MODULE_NOT_FOUND。

declared = enforced

注册方(setup-git-hooks.mjs)与校验方(git-merge-regen.mjs --self-test)此前各写各的;现在共读 regen-artifacts.mjs 里同一份 GIT_SETTINGS 声明,少一处可漂移的副本。

改动范围

scripts/setup-git-hooks.mjsscripts/regen-artifacts.mjsscripts/git-merge-regen.mjs,加一个空 frontmatter changeset(tooling 改动,不发版)。未改package.json,未改 .github/workflows/,未碰 content/docs/releases/

pnpm check:merge-driver 绿;三个改动脚本 ESLint 干净(exit 0);setup-git-hooks.mjs 重复运行仍然静默(幂等)。


Generated by Claude Code

`setup-git-hooks.mjs` 把绝对路径 `${REPO_ROOT}/scripts/git-merge-regen.mjs` 写进
`.git/config`。linked worktree 共用同一份 config,于是每次 `pnpm install` 都把全容器
的驱动改指向刚装完的那个 worktree;而 AGENTS.md 要求收尾时 `git worktree remove`
—— 遵守这条纪律恰恰就是触发缺陷的动作。该 worktree 一删,所有 agent 凡碰到
`merge=os-regen` 映射文件的 merge 全部 MODULE_NOT_FOUND。路径已漂过四个 worktree。

改为 `node "$(git rev-parse --show-toplevel)/scripts/git-merge-regen.mjs" %O %A %B %P`:
git 把 merge 驱动交给 shell 执行,命令替换在每次调用时、在正在被合并的那个工作树里
求值 —— 既不绑定任何具体 worktree,又仍能解析到当前工作树根(绝对路径当初正是为了
后者)。既有 clone 下次 `pnpm install` 自愈。

自检此前照不出这个缺陷:每条既有 `--self-test` 检查都自建临时仓库、注册自己的驱动,
所以真实 config 悬空时它们全绿。新增 `registeredDriverResolves()` 读**实时** config,
在脚本不存在、指向本工作树以外(缺陷咬人前一步)、或与注册值漂移时判红。注册方与
校验方现在共读 `regen-artifacts.mjs` 的同一份 `GIT_SETTINGS` 声明。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ
@vercel

vercel Bot commented Aug 3, 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)
objectstack Ignored Ignored Aug 3, 2026 3:34pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling size/m labels Aug 3, 2026
@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 3, 2026 15:36
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit 07ce845 Aug 3, 2026
18 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-4868-merge-driver-worktree-independent branch August 3, 2026 15:40
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 size/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

merge.os-regen.driver 指向「上一个装过依赖的 worktree」的绝对路径 —— 该 worktree 一删,全容器的生成物合并驱动就坏了

2 participants