Diagnosing Composer Dependency-Resolution Failures: A Step-by-Step Guide
Diagnostic guide for Composer's 'requirements could not be resolved' error: recognize condition, run ordered checks (validate, why-not, platform-reqs, lock hash), apply fixes for platform mismatches, shared-dependency conflicts, stale locks, stability blocks, and escalation criteria.
03 Apr 2026, 05:20 UTC

The Problem You're Facing
You run composer install or composer update and hit the dreaded message:
Your requirements could not be resolved to an installable set of packages
Followed by a wall of package names, version constraints, and conflict arrows. The immediate takeaway: Composer's SAT solver found no combination of package versions that satisfies every constraint in your composer.json plus the locked versions in composer.lock. This guide walks you through the most common causes, ordered checks, and targeted fixes — so you can resolve the conflict instead of guessing.
Recognizable Condition
The error always starts with the same line and then lists one or more problems. Each problem names a package and the constraints that cannot be satisfied simultaneously. Example:
Problem 1
- Root composer.json requires guzzlehttp/guzzle ^7.0 -> satisfiable by guzzlehttp/guzzle[7.0.0, ..., 7.8.x-dev].
- vendor/some-package requires guzzlehttp/guzzle ^6.0 -> satisfiable by guzzlehttp/guzzle[6.0.0, ..., 6.5.x-dev].
- Can only install one of: guzzlehttp/guzzle[6.0.0, ..., 6.5.x-dev, 7.0.0, ..., 7.8.x-dev].
This output tells you exactly which packages are pulling in incompatible ranges of a shared dependency.
Common Causes at a Glance
| Cause | Typical Symptom | Quick Verification |
|---|---|---|
| Platform constraint mismatch | Error mentions php or ext-* in the constraint list | composer check-platform-reqs and php -v |
| Incompatible shared dependency ranges | Two packages require different major versions of the same library (e.g., guzzle ^6 vs ^7) | Read the problem list; it names both constraints |
Stale or conflicting composer.lock | Lock file pins versions that no longer satisfy updated composer.json | Compare composer.lock content-hash with composer.json |
| Minimum-stability blocking dev/alpha releases | A required package only has dev, alpha, or beta releases | Check minimum-stability in composer.json and package release tags |
Ordered Diagnostic Checks
Run these in sequence. Each check either pinpoints the blocker or rules out a class of causes.
1. Validate composer.json Syntax
composer validate --strict
Run from the project root (where composer.json lives). No special permissions needed. A non-zero exit code with parse errors means fix the JSON first — missing commas, trailing commas, or invalid version strings are common culprits.
2. Identify the Blocking Constraint with why-not
composer why-not vendor/package
Replace vendor/package and with the package and version you expect to install (e.g., guzzlehttp/guzzle ^7.0). This command traces the dependency graph and prints the exact constraint chain preventing that version. If you're unsure which version to test, pick the one mentioned in the solver output.
3. Verify PHP Version and Extensions
composer check-platform-reqs
php -v
php -m | grep -E 'mbstring|xml|json|ctype|tokenizer|openssl|pdo|mbstring'
Run on the same machine/container where Composer executes. The first command compares your actual PHP runtime against every package's require section. Mismatches here are the single most frequent cause of resolution failures.
4. Inspect the Lock File Content Hash
cat composer.lock | grep -A2 '\"content-hash\"'
# Compare with a fresh hash:
composer update --dry-run --no-interaction 2>&1 | head -20
The content-hash in composer.lock is a SHA of the normalized composer.json. If they diverge, composer install will refuse to run (or produce unexpected versions). Regenerate with composer update after you've fixed the underlying constraints.
Fixes Tied to Findings
Platform Mismatch → Align Runtime or Simulate Target
If check-platform-reqs reports missing extensions or wrong PHP version:
- Preferred: Install the required PHP version/extensions on the target environment (CI, production, local).
- Only for CI metadata tasks: Add
"config": { "platform": { "php": "8.2" } }tocomposer.jsonto simulate a different PHP version during resolution. Never use--ignore-platform-reqsin production — it installs code that will fatal at runtime.
Incompatible Shared Dependency → Widen or Align Constraints
When two packages demand different major versions of a shared library (e.g., guzzlehttp/guzzle ^6 vs ^7):
- Check if the stricter package has a newer release supporting the wider range. Update that package first:
composer update vendor/strict-package --with-dependencies. - If you control the stricter package, relax its constraint in its own
composer.json(e.g., change^6.0to^6.0 || ^7.0). - As a last resort, fork the stricter package, adjust its constraints, and load your fork via a VCS repository in
composer.json.
Stale Lock File → Regenerate Selectively
composer update --lock
# or for a scoped update:
composer update vendor/package --with-dependencies
--lock regenerates composer.lock without installing packages (useful in CI). --with-dependencies also updates dependencies of the named package, which is often necessary when the conflict sits deeper in the graph.
Minimum-Stability Blocking a Needed Release → Per-Package Stability Flag
If a package only has dev/alpha/beta releases and your minimum-stability is stable (the default):
"require":{
"vendor/needed-package": "@dev"
}
The inline @dev (or @alpha, @beta) flag applies only to that package. Avoid lowering global minimum-stability — it pulls unstable versions of all dependencies.
Escalation Criteria
When the solver output is unreadably long (hundreds of lines), rerun with verbosity to see the decision trail:
composer update -vvv 2>&1 | less
# or test without writing the lock file:
composer update --dry-run
If a third-party package's constraints are genuinely wrong (e.g., it claims compatibility with a major version it doesn't support):
- Open an upstream issue with the solver output attached.
- If you need an immediate workaround, use composer-patches to apply a local patch to that package's
composer.json. - Do not use
--ignore-platform-reqsor--no-check-publishin production builds.
Verification After Applying a Fix
Once you've edited composer.json or regenerated the lock file:
composer validate --strict
composer check-platform-reqs
composer install --dry-run
The first confirms syntax and hash consistency. The second ensures your runtime still satisfies all platform requirements. The third does a full resolution pass without writing to disk — if it succeeds, the conflict is resolved.
Practical Example: Resolving a Guzzle 6 vs 7 Conflict
Suppose your project requires guzzlehttp/guzzle ^7.0 but some/legacy-client requires ^6.0. The solver output shows Problem 1 exactly as in the example above.
- Run
composer why-not guzzlehttp/guzzle ^7.0. It prints the chain:root -> some/legacy-client 2.0 -> guzzlehttp/guzzle ^6.0. - Check Packagist:
some/legacy-clienthas a 3.0 release supportingguzzlehttp/guzzle ^6.0 || ^7.0. - Update that package:
composer update some/legacy-client --with-dependencies. - Run verification steps. The lock file now pins Guzzle 7.x and the install succeeds.
Limitations and Gotchas
- Composer 1.x is end-of-life. All command behavior described here assumes Composer 2.x (
composer --versionshould show 2.x). - Editing
composer.lockmanually corrupts its content-hash and breakscomposer installvalidation. Always regenerate viacomposer update. why-notandprohibitsare aliases; output format may differ slightly between Composer 2 minor versions.- Private repositories or path repositories can introduce constraints not visible on Packagist — include them in your
why-notqueries if the conflict involves a private package.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.