文档索引在 Windows 上正常,上 Linux 就 404:一次跨平台死链自查
一次真实的排查起点
我在一个开源多智能体项目里做文档走查时,发现了一个几乎不会在本地暴露的问题:文档索引文件 SUMMARY.md 里写着这样的链接——
- [技能交响乐:技能检索、编排与分发](symphony-技能编排与分发.md)
而仓库里实际的文件名是 Symphony-技能编排与分发.md,首字母是大写的 S。
在 Windows 上点开完全正常,在 macOS 默认的 APFS 上也正常。但同一份仓库放到 Linux(Docker 构建、CI runner、静态文档站点生成)上,这个链接就是一个 404。
原因不复杂:NTFS 和默认配置的 APFS 是大小写不敏感的文件系统,symphony.md 和 Symphony.md 被当成同一个文件;而 ext4、overlayfs、以及绝大多数 CI 的运行环境都是大小写敏感的。文件系统层面能打开,链接才是"看起来没坏"。
为什么这类问题特别难自查
它同时满足三个"躲开所有检查"的条件:
- 本地复现不了:开发主力机是 Windows 或 macOS,永远走不到失败分支;
- 构建不报错:文件都提交了、Dockerfile 也能构建、单测也能跑,CI 绿得理直气壮;
- 只有人点的时候才炸:出问题的是读者,不是作者。而且在文档站点里往往表现为"翻到某一页白屏",连堆栈都没有。
更麻烦的是它有对称性。同一个坑,中英双语目录往往各踩一次:docs/en/SUMMARY.md 指向 symphony.md,docs/zh/SUMMARY.md 指向 symphony-技能编排与分发.md,而两个实际文件都是大写 Symphony 开头。修一处不算修完。
把它查干净的方法
手工逐个点太慢,而且总会漏。这里的关键是:不要靠渲染,直接比对"索引里写的文件名"和"仓库里真实存在的文件名"。
两步就够了。第一步,拿到仓库真实的文件清单(这里用 GitCode 的 contents API 逐层遍历,换成 git 命令也一样):
# 只需要目录与文件名,不下载内容
curl -s "$API/repos/$OWNER/$REPO/contents/$DIR" | jq -r '.[].name'
第二步,把索引里的相对链接全部抽出来,逐个做精确匹配(不是忽略大小写的匹配):
# 抽出 markdown 里的本地 md 链接
grep -oP '\]\(\K[^)]+\.md' docs/zh/SUMMARY.md \
| grep -v '/' \
| while read -r f; do
[ -e "docs/zh/$f" ] || echo "MISSING: docs/zh/$f"
done
[ -e ] 在 Linux 上是精确匹配的,所以大小写不一致会直接落到 MISSING 里。如果想让脚本在 Windows 上也能跑出同样的结论,就不要用 [ -e ],改成先列出目录清单、再做字符串精确比较——这一步很容易被忽略,但恰恰是"用不敏感的系统去检查敏感的问题"这个陷阱本身。
顺带提一个验证技巧:对同一个路径只改大小写,各发一次请求,比对 HTTP 状态码。404 与 200 摆在一起,任何人都无法用"我这边是好的"来反驳:
docs/en/symphony.md -> 404
docs/en/Symphony.md -> 200
docs/zh/symphony-技能编排与分发.md -> 404
docs/zh/Symphony-技能编排与分发.md -> 200
让它不再复发
查出来只是止血,真正要解决的是"下次提交还会这样"。仓库里既然已经有 repo-integrity 这类校验提交树完整性的 CI,把文档链接检查挂进去是最省事的位置——十行以内的 shell 就够:
set -eu
fail=0
for sum in docs/en/SUMMARY.md docs/zh/SUMMARY.md; do
dir="$(dirname "$sum")"
grep -oP '\]\(\K[^)]+\.md' "$sum" | grep -v '/' | while read -r f; do
if [ ! -e "$dir/$f" ]; then
echo "::error file=$sum::链接目标不存在(注意大小写):$f"
fail=1
fi
done
done
exit $fail
两个细节值得写进注释:
- 只在 Linux runner 上跑。放到 Windows runner 上跑等于白跑,因为不敏感的文件系统会把错误吞掉,得到一个"通过"的假信号。
- 只校验相对链接。外链会随网络和站点状态抖动,把它塞进 CI 会变成噪声,最后大家习惯性忽略红灯,检查就失效了。
可以带走的一份清单
写文档索引(SUMMARY.md、_sidebar.md、mkdocs.yml 的 nav、docusaurus 的 sidebars)时,下面这几条能挡掉绝大多数同类问题:
- 文件名统一用 PascalCase 或全小写,不要混;中文文件名尤其容易因为输入法或大写键产生首字母大小写漂移;
- 索引里指向的文件名,从文件系统复制,不要手打;
- 新增/重命名文档时,"改文件名"和"改索引"必须在同一个提交里;
- CI 至少加一条"索引链接可达性"检查,并且跑在区分大小写的系统上;
- 排查时不要用编辑器或浏览器的预览来判断,它们在不敏感系统上会给你假阴性。
最后一句经验:凡是"我本地是好的"这类反馈,先问一句对方的文件系统是不是区分大小写。这一个问题能省掉一大半来回。同样的逻辑也适用于 imports、package.json 的入口、Dockerfile 的 COPY 路径、以及 shell 脚本里的 source——它们全都吃大小写的亏,而且全都只在部署那天才发作。
- 点赞
- 收藏
- 关注作者
评论(0)