ci/cd 中 run-script 失败却不中断,根本原因是脚本实际退出码为0:curl缺-f、phpunit缺--fail-on-failure、php脚本未显式exit(1)、shell命令未加|| exit 1等均导致误判成功。

CI/CD 中 run-script 失败却不中断?检查退出码是否真为非零
Composer 默认会把脚本的退出码原样透传给 shell,但很多常见操作(比如 curl、phpunit、甚至某些 PHP 脚本)在逻辑失败时仍返回 0,导致 CI 流水线误判成功。
典型现象:审计脚本发现 critical 漏洞,但构建继续往下走;数据库迁移报错“Table already exists”,CI 却标记为 success。
-
curl -s https://api.example.com/health即使返回 HTTP 500,退出码仍是 0 —— 必须加-f(--fail)参数才让失败响应触发非零退出 -
phpunit默认不因测试失败而退出非零,得加--fail-on-warning --stop-on-failure - PHP 脚本里用
exit(0)或没写exit(),哪怕抛了异常,也容易被 Composer 吞掉 —— 显式写exit(1)最稳妥 - Shell 脚本中每条命令后加
|| exit 1,避免中间某步失败却被后续覆盖退出码
composer run-script 和 composer run 的退出码行为一致吗?
一致。两者底层都调用同一执行器,退出码完全由脚本本身决定,和调用方式无关。但 composer run(推荐)比 composer run-script 更早做参数校验,能提前暴露脚本名拼错、未定义等问题。
关键区别在于参数透传逻辑:
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
-
composer run test -- --filter=FooTest:中间的--是硬分隔符,确保--filter传给 phpunit,不是 Composer 自己吃掉 -
composer run-script test --filter=FooTest:这个写法会让 Composer 尝试解析--filter,大概率报错或静默忽略 - 数组型脚本(如
"@test:unit")在 Composer ≥2.2 才支持,旧版本直接跳过,且不报错 —— 建议 CI 中先跑composer --version校验
如何在 CI 中可靠捕获并响应非零退出码?
别依赖“脚本失败自动熔断”这种幻想。CI 平台对命令退出码的响应策略各不相同,必须显式声明失败条件。
- GitHub Actions:在
step中加if: ${{ failure() }}或用continue-on-error: false(默认就是 false,但显式写更安心) - GitLab CI:默认已启用
interruptible: true,但需确认 runner 配置未覆盖全局default: { tags: [...] }导致行为不一致 - 所有平台都建议在脚本末尾加一句验证:比如
composer audit --no-dev --format=json | jq -e 'any(.advisories[]; .severity == "critical" or .severity == "high")' > /dev/null || exit 1 - 避免用
|| true掩盖真实失败 —— 这是调试期临时手段,上线前必须删掉
post-install-cmd 不执行,是不是退出码没起作用?
不是。根本原因是它压根没触发,谈不上退出码生效。CI 环境下 post-install-cmd 几乎总是被跳过:vendor 目录存在、lock 文件完整、又开了 --no-dev,Composer 直接复用缓存,连依赖解析都不做。
真正可控的做法只有两个:
- 放弃依赖事件钩子,在 CI 步骤里显式调用:
composer run deploy:post-install,脚本名自己定义,不受环境变量限制 - 改用
post-autoload-dump:只要vendor/autoload.php有更新(install/update/dump-autoload 都会触发),它就一定执行,且退出码同样有效 - 如果用了 PHP callable(如
"MyClass::run"),确保该类已 autoload ——post-autoload-dump是安全时机,post-install-cmd可能太早
最易被忽略的点:退出码只对当前命令生效。如果你在 CI 脚本里写了 composer run lint && composer run test,前面 lint 失败会被 && 拦住;但写成两行独立命令,第二行照常执行。熔断逻辑必须靠 shell 控制流或 CI 平台原生机制来兜底,不能指望 Composer 自己串起来。










