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. added
    pm:queueReady for the PM dispatch loop
    domain:devxDomain lane — seat card indexed by label:pm:seat
    on Aug 3, 2026
  2. 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

  3. 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

Labels

domain:devxDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatchpm:queueReady for the PM dispatch loop

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions