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

  1. Pass a valid stability as the 3rd argument (default 'stable'): findBestCandidate($name, $targetVersion, 'stable').
  2. 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.
  3. Use one of the BasePackage::STABILITIES keys: 'stable', 'rc', 'beta', 'alpha', or 'dev'.
  4. 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

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


AI-assisted analysis of composer/composer@c435d285c9 (2026-08-07). Data as JSON: /api/errors/017b36f431bc357d. Report an issue: GitHub.