macOS exFAT 卷上 pnpm install 必现失败:install-lefthook 锁的 inode 所有权校验在 exFAT 上永不成立(stale lock 与 ownership changed 交替循环) #2402
Replies: 5 comments
|
Cross-checked the report against master (scripts/install-lefthook.mjs @ 47f9438) — the mechanism is accurate:
+1 on the degradation approach, and it costs no security:
One general observation, in the same direction as the #1846 check-lifecycle discussion: any "validate/anti-replacement" logic should not depend on identity facts provided by the filesystem. Once the validation method itself relies on an unstable environmental fact, the failure migrates from the validated object to the validation. dsh-doctor's E6 tripwire picks anchors by the same principle — the anchor must be something stable that exists in the installed artifact. |
|
Thanks for cross-checking the line numbers. Detection over config makes sense to me too. One detail on the probe idea: it has to run in the lock file's directory, not /tmp — same filesystem matters. The minimal repro in the post works as the probe as-is. I hit this on an external exFAT SSD (repo lives there for portability), so probably not a rare setup. |
|
Right — the probe must run on the same mount as the lock file; a /tmp probe on APFS would report "consistent" and never degrade. Using the minimal repro as the probe, in the lock directory, is the right shape. Two small notes:
If you want, I can sketch the concrete diff for |
|
exFAT 卷上 lefthook inode 校验永不成立——文件系统兼容坑(inode 语义在 exFAT 上不同)。和 #556(yaml UTF-8 自愈 bug)一起看,lefthook 在特殊环境的坑不少。 临时:仓库放回 APFS 卷,或跳过 lefthook。安装坑第 2 章有汇总:https://github.com/Electricitysheep/dsh-handbook/blob/main/docs/02-quickstart.md |
|
Why not just use the UUID record? Then it's filesystem independent as well. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
环境
diskutil显示 File System Personality: ExFAT)47f943859b,pnpm v11.7.0现象与原始输出
pnpm install在 postinstall 阶段退出 1,两种错误交替出现,删锁重跑也无法恢复。错误一(锁由上一次失败的 install 遗留),pnpm 实际输出(预先放置内容为
999999 <uuid>的锁文件模拟遗留死锁后捕获):错误二(手动删除锁文件后重跑),pnpm 实际输出:
直接复现(
node scripts/install-lefthook.mjs,输出逐字未改)复现错误一:先放置一个内容为
999999 <uuid>的锁文件(模拟上次失败遗留的死 PID 锁),然后运行:复现错误二(无锁的干净状态直接运行):
每次失败都会在
.git/遗留一个新锁文件,属主 PID 已随进程退出:于是下一次 install 又报 stale——形成循环,hooks 永远装不上(
core.hooksPath从未被写入,lefthook 从未安装)。相关源码(
scripts/install-lefthook.mjs@47f943859b)锁获取的关键路径(
acquireInstallLock,line 381 起)——先openSync('wx')创建并用 fd 记录ownedStat,关闭句柄后再按路径lstatSync复查并比对dev/ino:stale 判定分支(同函数 catch 内,line 450,以下为源码原文)——锁已存在且属主 PID 不可达时要求人工恢复:
根因分析
问题出在 macOS 的 exFAT 驱动报告的 file ID 不稳定,且变化点正好落在上述时序上:句柄仍打开时,fd 的
fstat与按路径的lstat返回同一个(合成的、接近UINT64_MAX的)ino;closeSync之后,按路径lstat得到的 ino 变成另一个小值。实测(exFAT 卷上新建文件):acquireInstallLock恰好在closeSync之后才做路径复查,因此 exFAT 卷上每次 install 都必然在取得锁之后的校验处抛lockOwnershipChangedError,进程退出后锁文件被遗留给下一次运行——这就是两种错误交替出现的完整链条。(对照实验:同一探测在 APFS 卷上句柄关闭前后 ino 均不变。)最小复现(复刻脚本的时序):
想讨论 / 请教
CI=true pnpm install可跳过 lefthook 安装(该脚本在 CI 环境下直接 return),依赖正常安装完成、退出码 0;或将仓库移至 APFS 卷(对照实验已确认 APFS 上该机制正常)。以上分析如有误,欢迎指正。
All reactions