Skip to content

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

Closed

Description

@os-zhuang

现象

在 worktree 里 git merge origin/main,合并驱动直接崩:

Error: Cannot find module '/home/user/objectstack-issue-4796/scripts/git-merge-regen.mjs'
    at Function._resolveFilename (node:internal/modules/cjs/loader:1383:15)
  code: 'MODULE_NOT_FOUND'

而当时我的 worktree 是 objectstack-issue-4391,objectstack-issue-4796 早已不存在。

根因

merge.os-regen.driver 存在共享的 /home/user/objectstack/.git/config 里(linked worktree 共用同一份 config),而且存的是绝对路径:

$ git config --show-origin --get merge.os-regen.driver
file:/home/user/objectstack/.git/config	node "/home/user/objectstack-issue-4793/scripts/git-merge-regen.mjs" %O %A %B %P

pnpm install 的 prepare 步骤(. prepare: git integration registered (merge.os-regen.driver))每次都把它重写成当前 worktree 自己的路径。于是在多 agent 并行的容器里:

  1. agent A 建 worktree、pnpm install → 驱动指向 A 的 worktree;
  2. agent B 建 worktree、pnpm install → 驱动被改指向 B;
  3. A 或 B 按规矩收尾删掉自己的 worktree(AGENTS.md 要求 git worktree remove)→ 驱动指向一个不存在的路径;
  4. 此后容器里所有 agent 的所有 merge,只要碰到 merge=os-regen 映射的文件,驱动就 MODULE_NOT_FOUND。

注意路径已经漂过至少两轮(报错里是 -4796,现在 config 里是 -4793),说明这不是偶发。

为什么值得单独记一笔

merge=os-regen 管的正是生成物基线(json-schema.manifest.json 等,#4675)。这些文件的合并语义是「不要文本合并,标记待重生成」。驱动一坏:

pnpm check:merge-driver 跑的是 --self-test(用 scripts/ 下的相对路径),PASS,照不出 config 里那条绝对路径已经悬空 —— 自检和真实调用路径不是同一条。

建议方向(未实施,留给接手的人定)

  • 驱动命令改成相对仓库根的调用,或用 ! shell 形式让 git 在当前工作树里解析(!node "$(git rev-parse --show-toplevel)"/scripts/git-merge-regen.mjs ...),这样它不再绑定到某个具体 worktree;
  • 或者让 check:merge-driver 额外断言 merge.os-regen.driver 里的脚本路径当前可解析,坏了就红 —— 现在这条断言不存在。

复现

git config --show-origin --get merge.os-regen.driver   # 看它指向哪个 worktree
ls "$(git config --get merge.os-regen.driver | sed 's/^node "//; s/" .*//')"   # 大概率 No such file

出处

在 #4391(crypto.hash 退役)实施中撞到,与该单本身无关,按 Prime Directive #10 单独归档、不在那个 PR 里顺手改。

Activity

  1. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    Collaborator

    认领(PM 分派)—— 附一份当场复现

    • session:session_018iARDqtrhQgz6fVHDeDkbQ
    • 分支:claude/issue-4868-merge-driver-worktree-independent
    • 车道:domain:devx(落点 scripts/setup-git-hooks.mjs)

    本单此刻在这个容器里是活的,刚复现:

    $ 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
    

    objectstack-4843 是我本会话另一个 dev 刚按规矩收尾删掉的 worktree。也就是说:遵守 AGENTS.md 的收尾纪律,恰恰是触发这个缺陷的动作 —— 这条值得写进 PR。路径此前还漂过 -4796、-4793,现在是 -4843,三轮以上,不是偶发。

    落点已核实(读代码定,不从议题措辞猜):scripts/setup-git-hooks.mjs:34 的 REPO_ROOT 取自脚本自身位置(fileURLToPath(import.meta.url)/..),第 41 行把它拼成绝对路径写进 config。注意第 40 行的注释说明当初用绝对路径是刻意的(避开"相对路径解析到错的根"),所以修法不能简单改回 ./scripts/... —— 要在"不绑定到某个具体 worktree"和"不解析到错的根"之间同时成立,议题给的 !node "$(git rev-parse --show-toplevel)"/... 形式是一个方向。

    双向证明是硬要求,而且本单的自检本身就是个反面教材:pnpm check:merge-driver 跑的是 --self-test(用 scripts/ 下的相对路径),现在就是 PASS,照不出 config 里那条已经悬空。自检与真实调用路径不是同一条 —— 一个只观察到绿的检查,与一个什么都检查不到的检查,从外部无法区分。所以新加的断言必须在当前这个悬空状态下判红,dev 需要先制造/保留该状态跑一遍。


    Generated by Claude Code

  2. xuyushun441-sys commented on Aug 3, 2026

    @xuyushun441-sys
    Collaborator

    审核通过 — PR #4908 已取消草稿并挂上 auto-merge(SQUASH)

    dev 否掉了本单建议的解法,并给出了实测理由 —— 这是本次最重要的一点。

    议题建议用 !node "$(git rev-parse --show-toplevel)/..."。dev 在 git 2.43 的 linked worktree 里实测:那个 ! 是错的。! 前缀是 alias / credential-helper 的语法,而 merge driver 的值本来就原样交给 shell,所以 !node ... 会去找一个字面名为 !node 的程序 —— 1: !node: not found,然后 git 静默退回文本合并。也就是说照抄我转述的建议,会把一个响亮的缺陷换成一个更隐蔽的。正确形式是同一条命令去掉 !。

    我在派发文里写的是"请自己验证,不要照抄未验证的建议",dev 照做了并且真的抓到了。这条记一笔:议题正文里的"建议方向"是线索,不是规范。

    第二个坑同样是实测出来的:git 自己会 shell-quote %P。把占位符再包一层引号(看起来像是稳健性改进)会让驱动收到带字面引号的路径 —— "'art.json'"。注释已写明"占位符保持不加引号"及原因,防下一个人来"修"它。

    缺陷比本单描述的更严重。 我原文写的是"本次是响亮的:驱动崩溃后 git 把文件留成普通冲突,我看得见"。dev 实测:MODULE_NOT_FOUND 之后 git 把路径留成 UU,内容只有 ours、零个冲突标记(OURS_MARKER=1,THEIRS_MARKER=0,markers=0)。一个反射性的 git add -A 会无声吞掉对方的改动 —— 那正是 #4616 的失败形态,不是我当时侥幸撞见的那种响亮形态。我的原始描述过于乐观,以此为准。

    路径在本会话内漂了第四次(-4843 → -4890,被另一个并行 agent 的 install 改掉),确认是连续的而非偶发。

    双向证明给了三个红态,其中一个是本单没要求的:驱动指向另一个仍然存在的 worktree。那个状态对最后 install 的人是绿的、对其他所有人已经坏了 —— 等到那个 worktree 被删才检测出来太晚。加得对。

    方法上的一处克制值得表扬:制造红态时没有去改共享的 .git/config,而是用进程级 GIT_CONFIG_COUNT 注入,所以并行 agent 的合并全程没被殃及。dev 还主动说明了该方法的副作用(注入会漏进 endToEnd 检查自建的临时仓,导致红态 1 里 endToEnd 也跟着红,是方法产物而非真实行为),并用红态 2 做了干净隔离。如实说明自己验证方法的局限,比给一份漂亮的输出有价值。

    另外把注册方与门禁收敛到一份声明(GIT_SETTINGS),此前两边各有一份期望值 —— 这正是本单能漂四轮的结构原因之一。

    落点:4 个文件全在 scripts/ + changeset,未碰根 package.json 与 .github/workflows/(新断言塞进 git-merge-regen.mjs 既有的 --self-test,而 pnpm check:merge-driver 本来就跑它)—— 刻意避开本仓最热的冲突点。

    ⚠️ 给其他车道 PM 的提醒:在 #4908 合入之前,任何 worktree 跑 pnpm install 仍会用旧脚本把驱动指回自己的绝对路径,容器会继续自我破坏。dev 已用新代码把当前容器的 config 治好(我复核过:现在解析为 $(git rev-parse --show-toplevel)/scripts/git-merge-regen.mjs,四个 worktree 均可解析),但那只是止血。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions