Windows 长路径 (MAX_PATH) 导致 node_modules 拷贝丢文件 (Windows MAX_PATH breaks node_modules copy)
一句话总结
在 Windows 上,只要是相对路径深度深、带 .pnpm 哈希目录的 node_modules,一旦目标目录的前缀比源目录更长,就极易触发 MAX_PATH (260 字符) 阈值,导致复制工具(如 robocopy)静默漏拷一批文件 —— 已拷的文件内容正确,但一整批超长路径文件根本没落盘。校验时最容易误判为"复制成功"。
何时会发生(触发条件)
- 目标目录的绝对路径长度明显大于源目录(例如源
D:\git-workspace\,目标C:\Users\xxx\.workspace\git-workspace\)。 - 被拷贝的是 pnpm 管理的
node_modules,.pnpm目录名是超长哈希串(如.pnpm@typescript-eslint+eslint-p_a1cc9da568f9a3d326c3b268daca1366\node_modules\...),单条文件路径很容易逼近/超过 260。 - 典型场景:用
robocopy/ 拖拽 / 压缩包解压,把一个仓库从D:\搬到C:\下更深的路径。
症状 / 报错特征
- 复制的日志里"失败 0",看起来一切正常(
robocopy摘要Files: ... 失败: 0),但目标端实际缺文件。 - 缺失文件集中出现在
.pnpm哈希目录内,多为.js、.d.ts、.map、*.node这类编译产物。 - 单文件路径长度刚好落在 260 附近(
len(dst_path) >= 260),而源路径仍在 260 以内(len(src_path) < 260)→ 源存在、目标不存在的"不对称缺失"。 - 用普通 API 校验目标时,超长路径文件既检查不到、也读不到,甚至被误当"缺失"反复重拷。
根因
Windows 的 Win32 API 默认以 260 个字符 (MAX_PATH) 为路径上限。源路径未超限所以文件能正常存在;目标路径因前缀更长而越限,创建文件时返回 FileNotFoundError 或 WinError 3 / 1920/ 206 等,文件未写入。robocopy 在 /R:0 /W:0 等"不重试"选项下会跳过失败文件但可能不把它们计入失败数,造成"成功"假象。
最终解决步骤(本次实操验证有效)
- 先确认真实缺失量:不要只信
robocopy /L的摘要。用脚本遍历源树,对比目标同相对路径是否存在。 用
\\?\长路径前缀强制读写:Windows 特供语法,\\?+ 盘符绝对路径可绕过MAX_PATH。bs = chr(92); q = chr(63) pre = bs + bs + q + bs # "\\?\" def long(p): p = os.path.abspath(p) return pre + p if not p.startswith(pre) else p逐文件补齐缺失:遍历源,对目标缺失的文件用
\\?\建目录 + 复制。os.makedirs(os.path.dirname(long(dst_path)), exist_ok=True) with open(long(src_path), "rb") as fr, open(long(dst_path), "wb") as fw: shutil.copyfileobj(fr, fw) # 读源、写目标都用 \\?\多线程可显著加速(
ThreadPoolExecutor)。- 校验也一律用
\\?\前缀:普通os.path.exists/os.path.getsize对超长路径会返回False/报错,导致无法确认是否真的拷成功。改用os.path.getsize(long(dst_path))比对大小,并对抽样文件做 md5。 - 若某些文件仍失败:多半是被运行中的进程占用(如
libpython3.14.so等运行时库),报WinError 1920 系统无法访问该文件,属正常占用,关闭相关进程后再补拷即可。
下次如何识别
- 拷贝含
node_modules的仓库后,不要只看复制工具的成功摘要。 - 看到
.pnpm超长哈希目录,或目标路径长度与 260 接近,就要主动做一次逐文件大小校验。 - 校验脚本若发现"源读取异常"或"目标缺失",先检查是否超长路径,而不是怀疑数据丢失。
相关概念
- Windows 长路径 (Long Paths) /
MAX_PATH限制 \\?\前缀 (Extended-length path prefix)- pnpm 的
.pnpm哈希目录结构 robocopy/shutil在长路径下的行为差异
可复用校验脚本(batch)
对复制目录做"存在性 + 大小 + 抽样 md5"三合一校验,统一用 \\?\ 前缀,能一次性暴露"看似成功实则丢文件"。
本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/nodejs