返回博客

目录名末尾多了个空格,Windows CI 直接 exit 128

某天往仓库里提交了一批静态资产,里面有按公司/机构名命名的子目录,名字是从 Word 文档直接粘过来的。在 macOS 本地一切正常,push 上去之后 Linux runner 也绿了。唯独 Windows runner 一直卡在第一步 Run actions/checkout@v4,连 setup-node 都没跑到就红叉退出:

invalid path 'asset/file/template/company/某科技有限公司 /合同文本-空白A4.doc'
The process 'C:\Program Files\Git\bin\git.exe' failed with exit code 128

把报错 copy 出来仔细看,才发现 某科技有限公司 后面跟着一个空格,再后面才是 /

现象

  • macOS / Linux clone 同一个 commit,完全没问题。
  • Windows runner(windows-latest)在 actions/checkout@v4invalid path,exit code 128。
  • 错误路径里的目录名紧跟着一个不易察觉的空格字符,CJK 字符在等宽字体下尤其难看出来。
  • find 可以精确定位:
find . -type d -name "* " -not -path "./.git/*"
# 有输出 = 有污染

根因

Win32 文件系统 API(CreateFile / WriteFile 等)有一条历史遗留规则:文件名和目录名末尾的空格或英文句点会被静默截断。微软文档明确写道:

"Do not end a file or directory name with a space or a period."

git.exe 在 Windows 下调用 WriteFile() 写出路径 某科技有限公司 / 时,OS 把它截断成 某科技有限公司/,导致 git 的 object checksum 与实际写出路径不一致,校验失败,exit 128。

macOS(APFS/HFS+)和 Linux(ext4 等)不做这个截断,所以本地从无感知。

坑:CJK 目录名尾随空格在 macOS 视觉上完全隐形

从 Word/PDF/邮件粘贴中文名称时,尾部常带有一个 U+0020(普通空格)或 U+3000(全角空格)。Finder、IDE、终端等宽字体下都看不出来,ls 也不会高亮,只有 find . -name "* " 才能抓到。偏偏 Win32 API 对这个字符零容忍,导致跨平台 CI 的奇异双标现象。

正解

git mv 两步走彻底清除尾空格。直接 git mv "a " "a" 在 macOS(大小写不敏感文件系统)上会报"源和目标相同",所以必须借一个临时名中转:

# 第一步:定位所有污染目录(从仓库根执行)
find . -type d -name "* " -not -path "./.git/*"
find . -type f -name "* " -not -path "./.git/*"
 
# 第二步:每个污染项两步改名
cd path/to/parent
git mv "某科技有限公司 " "某科技有限公司__rename_tmp"
git mv "某科技有限公司__rename_tmp" "某科技有限公司"
 
# 第三步:提交推送
git commit -am "fix: strip trailing space from dir names · Windows NTFS checkout"
git push

多个目录批量处理:

cd path/to/parent
for d in "公司甲 " "公司乙 "; do
  clean="${d% }"
  git mv "$d" "${clean}__rename_tmp"
  git mv "${clean}__rename_tmp" "$clean"
done
git commit -am "fix: strip trailing space · Windows CI"
git push

修复后验证:

find . -type d -name "* " -not -path "./.git/*"
# 应无输出
正解:git mv 两步走,从源头消灭尾空格

这是永久修复。git mv 不改变 blob 内容,rename detection 显示 100% similarity,reviewer diff 没有噪音。macOS/Linux checkout 不受影响——去掉一个无语义的尾空格对它们是零 breaking change。

别走 sparse-checkout 这条弯路

看到 invalid path 'asset/...' 很容易想到"把 asset/ 排除出 sparse-checkout 不就行了"。但 actions/checkout@v4 的 sparse-checkout pattern 解析逻辑和原生 git 有出入,/* + !/asset 经常无声失效,asset 目录照样被写出来,checkout 照样挂。更重要的是:repo 里的污染路径仍然存在,Windows 开发者拉取、未来新增的 Windows CI matrix 都会撞同一堵墙。治标不治本。

一句话外卖

跨平台 CI 的 Windows exit 128:先查路径里有没有尾空格或尾句点,find . -name "* " 比肉眼可靠一万倍。