prefer-stable仅是排序规则,非过滤器;它只在multiple候选版本均满足minimum-stability时优先选最稳定者,必须配合minimum-stability使用且需运行composer update --lock才生效。

prefer-stable 没有过滤作用,它不拦版本、不拒安装、不删候选 —— 它只在多个满足约束的版本里,悄悄把 stable 版排到前面。
prefer-stable 不是过滤器,而是排序规则
它不会让 dev-main、2.0.0-beta.1 或 3.1.0-rc 从候选池里消失;只要这些版本满足你的约束(比如 "monolog/monolog": "^3.0"),且没被 minimum-stability 挡住,它们就还在池子里。prefer-stable 做的唯一一件事:当池子里同时有 3.1.0(stable)和 3.2.0-beta.1(beta),它让 Composer 选前者。
常见错误现象:
- 改完
"prefer-stable": true后composer install还装了dev-develop—— 因为composer.lock已锁死该版本,install根本不重解 - 看到
composer show -a monolog/monolog列出一堆dev-main (dev)就以为会装 —— 实际是否装,取决于约束匹配和minimum-stability是否放行 - 在
require里写了"some/pkg": "dev-main",还指望 prefer-stable 拦住 —— 显式指定分支,Composer 直接跳过所有偏好逻辑
真正起效的前提:minimum-stability 必须设对
prefer-stable 单独存在时几乎无效。它的作用前提是:多个候选版本都“合法”——而谁合法,由 minimum-stability 决定。比如:
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
-
"minimum-stability": "stable"→ 候选池只含 stable 版,prefer-stable无意义(池里只剩一个级别) -
"minimum-stability": "beta"→ 池子里有 stable、RC、beta,这时prefer-stable: true才会让 Composer 在三者中优先 pick stable -
"minimum-stability": "dev"→ 池子最宽,包含所有级别,prefer-stable的价值最大:能确保symfony/console装6.4.0而非7.0.x-dev,哪怕你允许 dev
注意:minimum-stability 必须是小写字符串,RC 是唯一必须全大写的值,写成 rc 或 Rc 会报错 Invalid stability value "rc"。
改完配置后,不运行 update 就等于没改
composer.json 里加了 "prefer-stable": true 和 "minimum-stability": "stable",但只跑 composer install?那 vendor/ 和 composer.lock 完全不变。
- 要让新偏好参与解析,必须运行
composer update(重解全部依赖并更新 lock) - 想最小改动验证,用
composer update --lock:只重算版本并更新 lock 文件,不动 vendor/(适合 CI 中快速检查) - 只想修正某几个包,避免牵连,用
composer update monolog/monolog guzzlehttp/guzzle - 临时测试效果,命令行加
--prefer-stable:如composer require laravel/framework --prefer-stable,该参数优先级高于配置项
为什么你还是看到 dev 包?三个最常忽略的漏点
即使 minimum-stability + prefer-stable 都配对了,dev-main 仍可能进 vendor/,原因往往不在你自己的 composer.json:
- 某个你依赖的包(比如
acme/sdk)自己声明了"minimum-stability": "dev",且没设"prefer-stable": true,它的子依赖(如guzzlehttp/psr7)就可能被拉成dev-master - 你在
repositories里加了私有 Git 仓库,并用了"type": "vcs",但没配"no-api": true或 token,导致 Composer fallback 到 Packagist 的元数据,而那里可能标记了错误的稳定性 -
composer.lock里已存着"version": "dev-develop",而你只在本地改了配置、没 push lock 文件,CI 流水线拉的是旧 lock,自然照装旧版
生产环境上线前,建议加一行检查:grep -q '"version":"dev-' composer.lock && echo "ERROR: dev version detected" && exit 1 —— 真正管用的不是配置,是落地时的校验。

















