Composer "could not find a version matching your minimum-stability" for our brand-new package
We published an internal package to our private Packagist and every app failed to require it: could not find a version matching your minimum-stability (stable). The package had no tags, only a main branch, so Composer saw only dev-main. How stability flags, tags and branch aliases work.
We extracted shared audit logging code into a package, acme/audit-log, pushed it to a private Git
repository and added it to our private Packagist instance. The first application to use it ran:
composer require acme/audit-log
[InvalidArgumentException]
Could not find a version of package acme/audit-log matching your minimum-stability (stable).
Require it with an explicit version constraint allowing its desired stability.
The package page on our private Packagist showed it, with one version: dev-main.
What Composer sees
composer show acme/audit-log --all
name : acme/audit-log
versions : dev-main
No numbered versions. Composer builds versions from git tags (v1.0.0 becomes 1.0.0) and branches
(main becomes dev-main). The repository had no tags yet; the author planned to tag "when it is stable".
The application's settings
{
"minimum-stability": "stable",
"prefer-stable": true
}
minimum-stability: stable means Composer only accepts stable versions unless told otherwise. dev-main
has stability dev, so it was filtered out, and there was nothing left.
What we ruled out
- Authentication to the private repository. Composer listed the package and its
dev-mainversion, so it could read the repository. - A typo in the package name. The error named the right package.
- Platform requirements. PHP and extension mismatches produce a different error explaining which requirement failed.
Root cause
The new package had no tagged releases. The only available version was the branch version dev-main, with
dev stability. The application's minimum-stability of stable excluded it, so Composer found no
acceptable version.
Stability levels
Composer assigns each version a stability, from least to most stable: dev, alpha, beta, RC,
stable. It derives it from the version string:
| Version source | Example | Stability |
|---|---|---|
| Branch | dev-main, 2.x-dev |
dev |
| Tag with suffix | 1.0.0-alpha.1, 1.0.0-beta2, 1.0.0-RC1 |
alpha, beta, RC |
| Plain tag | v1.0.0 |
stable |
minimum-stability sets the lowest stability allowed for all packages by default. Most applications use
stable, which is the right default: you do not want development versions of transitive dependencies
slipping in.
Ways to allow a less stable version
- A stability flag on one constraint:
"acme/audit-log": "dev-main"or"^1.0@beta". Only that package may use a lower stability. - Lower
minimum-stabilityfor the whole project, usually withprefer-stable: true. Affects every dependency. - Branch alias: the package declares that
dev-mainrepresents an upcoming version, so constraints like^1.0@devcan match it.
Who is affected
Anyone publishing a new package without tags, internal packages consumed before their first release, and
forks used through VCS repositories. It also appears when a package only has pre-release tags such as
1.0.0-beta1 and the application requires ^1.0 with stable minimum stability.
Reading the error message correctly
The second sentence of the error, "Require it with an explicit version constraint allowing its desired
stability", is Composer telling you exactly which lever exists. It is easy to read it as "lower your
minimum-stability", which is the wrong lever. The intended meaning is a constraint such as dev-main or
^1.0@beta on that one package, and even better, a real tagged release so no special constraint is needed.
Environment
| Component | Version / value |
|---|---|
| Composer | 2.8 |
| Package | acme/audit-log, private Git repository, private Packagist |
| Versions available | dev-main only |
| App settings | minimum-stability: stable, prefer-stable: true |
The real fix: tag a release
The package was ready for use by the first app, so it deserved a version. We tagged it:
git tag v0.1.0
git push origin v0.1.0
Our private Packagist picked up the tag through its webhook. Then:
composer require acme/audit-log:^0.1
Versions below 1.0 signal that the API may still change. With Composer's caret operator, ^0.1 allows
0.1.x but not 0.2.0, because for 0.x versions the minor number is treated as breaking. That matched our
intent: apps opt in to each minor release while the API settles.
If you must use the branch: a scoped stability flag
During development, before the first tag, an app can require the branch explicitly without lowering stability for everything:
"require": {
"acme/audit-log": "dev-main"
}
This pins the app to whatever is on main, which changes under it. We used this only in a feature branch of
the first consuming app, never in main.
Branch alias for continuous development
The package now declares a branch alias, so dev-main is understood as the upcoming 0.2 line:
"extra": {
"branch-alias": {
"dev-main": "0.2.x-dev"
}
}
An app testing unreleased changes can require "acme/audit-log": "^0.2@dev", which matches dev-main
through the alias now and the real 0.2.0 tag later, without changing the constraint.
What not to do
Changing the application's minimum-stability to dev makes Composer consider development versions of
every dependency. With prefer-stable: true it usually still picks stable ones, but any package whose
constraints cannot be met by stable versions will silently come from a branch. We reverted that change in a
pull request where someone had tried it.
Alternatives and trade-offs
- Path repositories for local development of a package next to an app, with symlinks. Convenient while developing both at once; not for production installs.
- Pre-release tags (
v1.0.0-beta.1) with@betaflags, for packages that want explicit testing phases.
Private Packagist and tags
Our private Packagist updates a package when the repository's webhook fires. Tags pushed before the webhook was configured were not picked up until we triggered a manual update. If a new tag does not appear, check the webhook deliveries in the repository settings before suspecting Composer.
How to test the fix
composer show acme/audit-log --all
# versions : dev-main, 0.1.0
composer why-not acme/audit-log 0.2.0 # explains which constraints block a version
composer why-not (alias prohibits) is the fastest way to understand why a specific version cannot be
installed.
Confirming in production
The first app installed 0.1.0 from the lock file in CI and production builds. Two more apps adopted the
package at ^0.1, and the 0.2.0 release a month later was rolled out app by app, each with its own pull
request.
What the team agreed
The underlying disagreement was about when a package is "ready" for a version. We agreed that the first
time another repository depends on a package, it gets a 0.x tag, and 1.0.0 comes when its API has been
stable through two minor releases. That made tagging a routine step rather than a statement of quality.
Regression guard
- The package's release checklist requires a tag and a changelog entry; CI on the package repository creates releases from tags.
- A check in application CI rejects
dev-*constraints andminimum-stabilityother thanstableon the main branch.
Security implications
Installing from branches means production runs whatever was last pushed, without a reviewed release, and
supply chain controls such as signed tags or release approvals do not apply. Tagged releases, lock files and
stable minimum stability keep what reaches production deliberate. Private Packagist access tokens used in
CI are read-only and scoped to the organisation's packages.
Prevention checklist
- Tag a version as soon as another project depends on a package.
- Keep applications at
minimum-stability: stable. - Use scoped stability flags, not global stability changes, for temporary dev versions.
- Add a branch alias for packages under active development.
- Use
composer why-notto diagnose unresolvable versions.
References
- Composer, minimum-stability: https://getcomposer.org/doc/04-schema.md#minimum-stability
- Composer, Versions and constraints, stability flags: https://getcomposer.org/doc/articles/versions.md
- Composer, Aliases and branch-alias: https://getcomposer.org/doc/articles/aliases.md
- Composer CLI, why-not: https://getcomposer.org/doc/03-cli.md#prohibits-why-not
Add a label
Embed this post
<iframe src="https://www.hibug.me/composer-new-package-minimum-stability-not-found" width="700" height="420" frameborder="0" loading="lazy"></iframe>
I have been writing PHP since before Composer existed and Laravel since its early versions. Today I build modular Laravel applications for payment and subscription businesses and maintain a few open-source packages in my spare time. I write about the practical side of that work: structuring large Laravel codebases, publishing and versioning packages, two-factor authentication that actually works for every user, and what really happens during an on-call night on a payments platform. Each post comes from something I shipped or fixed, with code you can adapt.
Comments
0No comments yet. Be the first to comment!
Sign In to post a comment.