根本原因是ZIP解压和Git不保留执行位,导致vendor/bin脚本缺x权限;应通过umask控制安装时权限,而非依赖post-install-cmd chmod。

直接改权限不是 Composer 的工作,而是你得让系统在安装那一刻就生成带正确权限的文件——否则每次 install 都得手动修,治标不治本。
为什么 vendor/bin 下的脚本总报 Permission denied
Linux/macOS 上运行 vendor/bin/phpunit 报错,常见现象是:Permission denied,但 ls -l vendor/bin/phpunit 显示权限为 -rw-r--r--(缺 x 位)。根本原因有两个:
- ZIP 包解压时 Unix 执行位丢失(PHP 的
ZipArchive不保留x) - Git 默认不跟踪文件权限变更,所以即使包作者设了
x,clone 或 install 后也失效
临时救急可用:chmod +x vendor/bin/phpunit,但下次 composer install 又会被覆盖。
用 post-install-cmd 自动赋权的坑在哪
很多人在 composer.json 里加:
"scripts": {
"post-install-cmd": "chmod +x vendor/bin/*",
"post-update-cmd": "chmod +x vendor/bin/*"
}
这看似一劳永逸,实际有硬伤:
- Windows 下直接失败(
chmod命令不存在),整个 install 流程中断 - 如果
vendor/bin/是符号链接(比如 Docker volume 挂载),chmod -R会穿透到源目录,误改宿主机权限 - CI 环境常用非 root 用户执行,
chmod可能因权限不足静默失败,难以排查 - 给所有文件加
x位不安全——比如某些配置文件不该可执行
更稳妥的做法是只对已知脚本赋权,例如:chmod +x vendor/bin/phpunit vendor/bin/psalm,而非通配 *。
真正该控制的不是 chmod,而是 umask 和属主
权限问题的源头不在 install 之后,而在 install 之前——系统怎么创建这些文件。关键参数是 umask:
-
umask 002→ 新建目录默认775,文件默认664(适合组协作) -
umask 000→ 目录777、文件666(慎用,安全风险高)
执行前先设好再装:
umask 002 && composer install
装完再加固一次(X 表示“仅对目录和已有 x 位的文件加执行权”,比 x 安全):
chmod -R u+rwX,g+rwX vendor/
如果曾误用 sudo composer install,先修复属主:sudo chown -R $(whoami):$(whoami) vendor/。
Windows 下别折腾 chmod,换调用方式
Windows 根本不识别 #!/usr/bin/env php,chmod +x 完全无效。报“不是内部或外部命令”不是权限问题,是执行机制不同。
三个务实选择:
- 绕过:统一用
php vendor/bin/phpunit显式调用(推荐,跨平台、稳定) - 补包装器:在
vendor/bin/下手写phpunit.bat,内容为@php "%~dp0phpunit" %* - 换工具:优先选自带
.bat的包(如laravel/installer的artisan)
别试 Set-ExecutionPolicy 或 WSL 的 chmod —— Windows PowerShell 的执行策略管的是 .ps1,跟 PHP 脚本无关。


















