composer status默认退出码为0且无输出,不代表vendor干净,仅表示source模式安装的包未修改;CI中需显式判断退出码(如composer status || exit 1)才能拦截篡改。

为什么 composer status 不会自动报错,但 CI 里必须让它失败
composer status 默认成功时不输出任何内容,退出码是 0;只有发现修改时才打印警告并返回 1。这意味着你在本地敲完命令没看到输出,很容易误以为“一切正常”,其实只是它默认静默。CI 流程中若不显式判断退出码,就会跳过这个检查。
- 正确写法:
composer status || { echo "vendor modified!"; exit 1; } - 错误写法:
composer status; echo "done"(即使 vendor 被改了也继续往下走) - Git 钩子中可用:
git diff --quiet vendor/ 2>/dev/null || { echo "Don't modify vendor/"; exit 1; },但注意这只对 Git 安装的包有效
哪些包会被 composer status 漏掉?path 类型和 ZIP 包的盲区
composer status 只检查由 Composer 正常安装、且能获取原始快照的包。两类常见情况它完全不扫描:
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
-
repositories中 type=package 或 type=path 的本地包 —— 它们没有 dist shasum,也没有 Git HEAD 记录,status 直接跳过 - 用
--prefer-source安装但未配置 Git remote 的包,或 zip 包被手动解压后又改过 —— 没有可比对的哈希源,status 无法确认是否改动 - 如果你在
composer.json里写了"my/package": "dev-main as 1.0.0"并指向本地路径,composer status根本不会把它列出来
看到 “modified” 别急着重装,先分清是缓存还是真篡改
运行 composer status -v 后发现一堆文件标为 modified,大概率不是你手改的 —— Symfony 缓存、Laravel 的 storage/framework 写入、Monolog 的日志目录等,都可能落在 vendor 子目录里,而 Git 忽略规则没覆盖它们。
- 先检查路径:如果 modified 文件在
vendor/symfony/cache/Symfony/Component/Cache/Adapter/这类 runtime 目录下,基本是正常行为 - 对比
git status --ignored=matching vendor/,看是否真有 untracked 或 modified 状态 - 确认是否用过
composer install --no-scripts:某些包的post-install-cmd没执行,导致生成文件缺失,也会被 status 当作“被删”而报 modified
编辑器只读 + Git 钩子,比靠人记更可靠
光靠定期跑 composer status 是被动防御。真正防住误改,得从源头切断保存路径:
- VS Code 中安装
Read Only Files插件,把vendor/**加进readOnlyFiles设置,保存时直接弹窗拒绝 - 在
.git/hooks/pre-commit里加一行:git diff --quiet vendor/ || { echo "vendor/ modified — aborting commit"; exit 1; } - 注意:
pre-commit钩子对未git add的修改无效,所以还得配合编辑器层防护
vendor/my-hack)完全无感,它只认 vendor/{vendor-name}/{package-name} 结构。这类“伪 vendor 文件”既不会被 status 扫到,也不会被 composer install 清掉,却可能污染自动加载逻辑。

















