一句话总结

在 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 等"不重试"选项下会跳过失败文件但可能不把它们计入失败数,造成"成功"假象。

最终解决步骤(本次实操验证有效)

  1. 先确认真实缺失量:不要只信 robocopy /L 的摘要。用脚本遍历源树,对比目标同相对路径是否存在。
  2. 用 \\?\ 长路径前缀强制读写: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
  3. 逐文件补齐缺失:遍历源,对目标缺失的文件用 \\?\ 建目录 + 复制。

    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)。

  4. 校验也一律用 \\?\ 前缀:普通 os.path.exists / os.path.getsize 对超长路径会返回 False/报错,导致无法确认是否真的拷成功。改用 os.path.getsize(long(dst_path)) 比对大小,并对抽样文件做 md5。
  5. 若某些文件仍失败:多半是被运行中的进程占用(如 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