composer/composer · error · UnexpectedValueException
Expected a valid stability name as 3rd argument, got %s
Error message
Expected a valid stability name as 3rd argument, got %s
What it means
Thrown by VersionSelector::findBestCandidate() when the $preferredStability argument (3rd positional arg) is not a key of BasePackage::STABILITIES (stable/rc/beta/alpha/dev). The inline comment notes this is specifically to catch callers still using the Composer 1.x signature, where the 3rd argument was the PHP version string rather than a stability.
Source
Thrown at src/Composer/Package/Version/VersionSelector.php:75
$this->platformConstraints[$package->getName()][] = new Constraint('==', $package->getVersion());
}
}
}
/**
* Given a package name and optional version, returns the latest PackageInterface
* that matches.
*
* @param PlatformRequirementFilterInterface|bool|string[] $platformRequirementFilter
* @param IOInterface|null $io If passed, warnings will be output there in case versions cannot be selected due to platform requirements
* @param callable(PackageInterface):bool|bool $showWarnings
* @return PackageInterface|false
*/
public function findBestCandidate(string $packageName, ?string $targetPackageVersion = null, string $preferredStability = 'stable', $platformRequirementFilter = null, int $repoSetFlags = 0, ?IOInterface $io = null, $showWarnings = true)
{
if (!isset(BasePackage::STABILITIES[$preferredStability])) {
// If you get this, maybe you are still relying on the Composer 1.x signature where the 3rd arg was the php version
throw new \UnexpectedValueException('Expected a valid stability name as 3rd argument, got '.$preferredStability);
}
if (null === $platformRequirementFilter) {
$platformRequirementFilter = PlatformRequirementFilterFactory::ignoreNothing();
} elseif (!($platformRequirementFilter instanceof PlatformRequirementFilterInterface)) {
trigger_error('VersionSelector::findBestCandidate with ignored platform reqs as bool|array is deprecated since Composer 2.2, use an instance of PlatformRequirementFilterInterface instead.', E_USER_DEPRECATED);
$platformRequirementFilter = PlatformRequirementFilterFactory::fromBoolOrList($platformRequirementFilter);
}
$constraint = $targetPackageVersion ? $this->getParser()->parseConstraints($targetPackageVersion) : null;
$candidates = $this->repositorySet->findPackages(strtolower($packageName), $constraint, $repoSetFlags);
$minPriority = BasePackage::STABILITIES[$preferredStability];
usort($candidates, static function (PackageInterface $a, PackageInterface $b) use ($minPriority) {
$aPriority = $a->getStabilityPriority();
$bPriority = $b->getStabilityPriority();
// A is less stable than our preferred stability,View on GitHub (pinned to c435d285c9)
Solutions
- Pass a valid stability as the 3rd argument (default 'stable'): findBestCandidate($name, $targetVersion, 'stable').
- If you were passing a PHP version, remove it - the Composer 2 signature no longer takes php version here; platform filtering is handled via $platformRepo.
- Use one of the BasePackage::STABILITIES keys: 'stable', 'rc', 'beta', 'alpha', or 'dev'.
- Update code/tutorials from the Composer 1.x API to the 2.x API.
Example fix
// before (Composer 1 signature - wrong)
$versionSelector->findBestCandidate('vendor/pkg', '^2.0', '7.4.0');
// after (Composer 2 signature)
$versionSelector->findBestCandidate('vendor/pkg', '^2.0', 'stable'); Defensive patterns
Strategy: validation
Validate before calling
// Validate stability against the known set before calling.
$stability = strtolower((string) $preferredStability);
if (!isset(\Composer\Package\BasePackage::STABILITIES[$stability])) {
throw new \InvalidArgumentException("Invalid stability: $preferredStability");
}
$pkg = $selector->findBestCandidate($name, $target, $stability); Type guard
/** @param string $s */
function isValidStability(string $s): bool {
return isset(\Composer\Package\BasePackage::STABILITIES[strtolower($s)]);
} Try / catch
try {
$pkg = $selector->findBestCandidate($name, $target, $preferredStability);
} catch (\UnexpectedValueException $e) {
// invalid stability - default to 'stable' (Composer 2 signature)
$pkg = $selector->findBestCandidate($name, $target, 'stable');
} Prevention
- Use the Composer 2 signature: 3rd arg is a stability (stable/rc/beta/alpha/dev), not a php version.
- Default the 3rd argument to 'stable' unless you intentionally want less stable packages.
- Update code and docs migrated from Composer 1.x.
When it happens
Trigger: Calling $versionSelector->findBestCandidate($name, $targetVersion, $phpVersion) using the old Composer 1 calling convention where the 3rd arg was the php version (e.g. '7.4.0'), which is not a valid stability name. Also any call passing a typo'd or unsupported stability.
Common situations: Code/tutorials written for Composer 1 calling findBestCandidate() with a php version as the 3rd arg; passing a custom stability string that isn't one of stable/rc/beta/alpha/dev; copy-paste from outdated docs.
Related errors
- No lockfile found. Unable to read locked packages
- Could not activate plugin "%s" as "%s" does not implement Co
- Your composer.lock was generated before the allow-plugins se
AI-assisted analysis of composer/composer@c435d285c9 (2026-08-07).
Data as JSON: /api/errors/017b36f431bc357d.
Report an issue: GitHub.