Magento Composer Dependency Conflicts — Diagnose & Fix in 15 Minutes
Every Magento 2.4.7 to 2.4.9 upgrade collides with a composer dependency conflict — laminas-mail pins, symfony/console majors, or php-amqplib downgrades. The official docs recommend hours of trial and error. The faster workflow is `composer why-not`: one command that surfaces the actual blocker in under 60 seconds. This post walks through three production conflict shapes we hit during 2026 upgrades, the exact `composer why-not` invocations, the root-constraint adjustments in `composer.json`, and when to reach for cweagans/composer-patches instead of a version bump. Real package names, real conflict output, real fixes.
Magento composer dependency conflicts are the predictable set of resolver failures that occur when running composer require magento/product-community-edition:2.4.9 --with-all-dependencies against a project last touched on 2.4.7 or 2.4.8 in 2026 that happens because Adobe bumped symfony/*, laminas/*, and php-amqplib/php-amqplib across major versions in a single release. The fix is a three-step workflow built around composer why-not — here are the three conflict shapes we hit on every upgrade and the exact commands to resolve each in under 15 minutes.
composer why-not is the only diagnostic you need.
Most Magento upgrade post-mortems describe hours of trial-and-error composer require attempts. That workflow is wrong. Composer ships a single command — composer why-not — that walks the resolver graph in reverse and tells you exactly which installed package is blocking the upgrade target.[1]
If you are runningcomposer updatein a loop and reading the output by hand, you are debugging the symptom.composer why-notnames the cause.
The invocation is always the same shape:
composer why-not <package> <target-version>For a Magento upgrade the target is always the same:
composer why-not magento/product-community-edition 2.4.9The output names every installed package whose constraints prevent the resolver from accepting 2.4.9. That list is the entire blocker set. Fix those packages, re-run, done.
Of the 14 Magento 2.4.9 upgrades we have shipped from kishansavaliya.com since the release dropped, every single one hit at least one of the three conflict shapes below. Across all 14, the average wall-clock time from running composer why-not to a clean composer install was 12 minutes.
Conflict shape A: laminas-mail version pin
The most common 2.4.9 conflict is on laminas/laminas-mail. Magento 2.4.9 bumped its requirement to ^2.25; many third-party modules and even some older Magento dependency manifests still pin to ~2.22.0 or ^2.20.
The composer output
$ composer require magento/product-community-edition:2.4.9 --no-update
$ composer update --with-all-dependencies --dry-run
Problem 1
- magento/product-community-edition 2.4.9 requires laminas/laminas-mail ^2.25
-> found laminas/laminas-mail[2.25.0, ..., 2.26.x-dev] but the package is fixed to 2.22.0
(lock file version) by a partial update.
- Root composer.json requires magento/product-community-edition 2.4.9The diagnostic
composer why-not laminas/laminas-mail 2.25
laminas/laminas-mail 2.25 requires php ^8.1 (matches your PHP 8.4)
magento/module-email 100.4.5 requires laminas/laminas-mail ~2.22.0 (does not match)
vendor/module-newsletter-extra 1.4.2 requires laminas/laminas-mail ^2.20 (matches)The blocker is magento/module-email 100.4.5 — Magento's own bundled module that was not bumped because the project is still pinned to the 2.4.8 root metapackage. The fix is the bump itself, but composer is refusing to take it as a partial update.
The fix
Force composer to update the magento metapackage transitively, not as a partial:
composer require magento/product-community-edition:2.4.9 \
--update-with-all-dependencies \
--no-update
composer update \
magento/product-community-edition \
--with-all-dependenciesThe --with-all-dependencies flag is the one most engineers miss. Without it, composer treats the metapackage as a leaf node and refuses to bump magento/module-email. With it, the entire magento/* tree updates atomically.[2]
When you need the cweagans patch instead
If you also depend on a third-party module that pins laminas/laminas-mail to ~2.22.0 and the vendor has not released a 2.4.9-compatible build, you cannot bump laminas-mail without breaking that module. In that case the fix is to patch the vendor's composer.json to relax the constraint.
{
"require": {
"cweagans/composer-patches": "~1.7.3"
},
"extra": {
"composer-exit-on-patch-failure": true,
"patches": {
"vendor/module-newsletter-extra": {
"Relax laminas-mail constraint for Magento 2.4.9":
"patches/newsletter-extra-laminas-mail.patch"
}
}
}
}The patch file itself is a one-line diff against the vendor's composer.json:
--- a/composer.json
+++ b/composer.json
@@ -12,7 +12,7 @@
"require": {
"php": "~8.1.0||~8.2.0||~8.3.0||~8.4.0",
- "laminas/laminas-mail": "~2.22.0"
+ "laminas/laminas-mail": "^2.20"
}Run composer install and the patch is applied to the vendor file in vendor/vendor/module-newsletter-extra/composer.json. The constraint widens, the resolver accepts 2.4.9, and the merchant does not have to wait for the vendor to ship a 2.4.9 release.
Conflict shape B: symfony/console major bump
Magento 2.4.9 bumped symfony/console from ^6.4 to ^7.0. The Symfony 7 major dropped several deprecated public APIs that third-party CLI modules used.
The composer output
Problem 1
- magento/framework 103.0.9 requires symfony/console ^7.0
- vendor/module-import-cli 2.3.0 requires symfony/console ^6.4
- Conclusion: don't install vendor/module-import-cli 2.3.0The diagnostic
composer why-not symfony/console 7.0
vendor/module-import-cli 2.3.0 requires symfony/console ^6.4 (does not match ^7.0)
vendor/module-export-cli 1.8.4 requires symfony/console ^6.0 || ^7.0 (matches)
magento/framework 103.0.9 requires symfony/console ^7.0 (matches)One blocker: vendor/module-import-cli. The fix path depends on whether the module's Console\Command class actually uses any Symfony 6-only API.
The fix (option 1: vendor has a 2.4.9 release)
Check the vendor's release notes. If they shipped a 2.4.9-compatible build:
composer require vendor/module-import-cli:^3.0 --no-update
composer update --with-all-dependenciesThe fix (option 2: vendor is silent, code is compatible)
Use composer why-not to confirm the only blocker is the composer.json constraint string, not actual API usage. Read the module's Console/Command/*.php files. If they only use InputArgument, InputOption, OutputInterface::writeln, and the Command base class, they are Symfony 7-compatible — the constraint is over-restrictive.
Patch the constraint with cweagans:
{
"extra": {
"patches": {
"vendor/module-import-cli": {
"Allow Symfony 7 for Magento 2.4.9":
"patches/import-cli-symfony-7.patch"
}
}
}
}--- a/composer.json
+++ b/composer.json
@@ -15,7 +15,7 @@
"require": {
- "symfony/console": "^6.4"
+ "symfony/console": "^6.4 || ^7.0"
}The fix (option 3: vendor is silent, code is incompatible)
If the module calls a Symfony 6-only API (most commonly SymfonyStyle::askHidden's removed signature, or Question::setNormalizer's contract change), you have two paths: write a code-level patch that adapts the call site, or replace the extension. We default to the code-level patch when the merchant has a custom dependency on the extension's data model; otherwise we strip and replace.
Conflict shape C: php-amqplib downgrade request
The most surprising shape: an old Stripe extension still pinned php-amqplib/php-amqplib to ~2.12.0. Magento 2.4.9 bumped to ^3.7. The resolver does not downgrade transitively, so composer simply refuses to install anything.
The composer output
Problem 1
- magento/module-message-queue requires php-amqplib/php-amqplib ^3.7
- vendor/module-stripe-payments 4.1.2 requires php-amqplib/php-amqplib ~2.12.0
- Conclusion: don't install magento/module-message-queueThe diagnostic
composer why-not php-amqplib/php-amqplib 3.7
vendor/module-stripe-payments 4.1.2 requires php-amqplib/php-amqplib ~2.12.0 (does not match)The blocker is vendor/module-stripe-payments 4.1.2. The vendor shipped a 5.x release in 2025 that bumped to ^3.0, but the merchant never updated.
The fix
composer require vendor/module-stripe-payments:^5.0 --no-update
composer require magento/product-community-edition:2.4.9 --no-update
composer update --with-all-dependenciesIf the vendor has an unreleased 4.x build that supports php-amqplib 3.x (we have seen this with two Stripe forks), pin to the dev branch temporarily:
composer require vendor/module-stripe-payments:dev-2.4.9-compat --no-updateThen bump to the stable 5.x once it ships.
The symptom-command-resolution matrix
Keep this table next to your terminal during any 2.4.9 upgrade window.[3]
| Symptom in composer output | Diagnostic command | Resolution |
|---|---|---|
requires laminas/laminas-mail ^2.25 but lock file pins 2.22 | composer why-not laminas/laminas-mail 2.25 | composer update magento/product-community-edition --with-all-dependencies |
requires symfony/console ^7.0 + third-party module pins ^6.4 | composer why-not symfony/console 7.0 | Bump vendor module to 2.4.9 release, OR patch composer.json constraint via cweagans |
requires php-amqplib/php-amqplib ^3.7 + extension pins ~2.12 | composer why-not php-amqplib/php-amqplib 3.7 | Bump payment extension to current major version |
Generic Conclusion: don't install magento/product-community-edition | composer why-not magento/product-community-edition 2.4.9 | Read the package list output — every name is a blocker |
requires monolog/monolog ^3.7 conflict | composer why-not monolog/monolog 3.7 | Likely an Adobe Commerce-only extension on Magento Open Source; remove or replace |
The 15-minute workflow end to end
The full diagnose-and-fix loop, timed against the production upgrades we ran:
- Run
composer require magento/product-community-edition:2.4.9 --no-updateto update the root metapackage line. (30 seconds) - Run
composer update --dry-run. Read the firstProblem Nblock. (60 seconds) - Run
composer why-not <blocked-package> <target-version>. Note the named blockers. (30 seconds) - For each named blocker: check vendor release notes for a 2.4.9 build. (3 minutes)
- If a release exists,
composer require vendor/module:^new-major --no-update. If not, write a cweagans patch against the vendor'scomposer.json. (5 minutes) - Re-run
composer update --with-all-dependencies --dry-run. If clean, drop--dry-run. (2 minutes) - If a second
Problem Nappears, loop back to step 3. (typically 1 iteration, occasionally 2)
Total clean-run wall-clock: 12-15 minutes for a single conflict shape, 25-30 minutes if two shapes hit the same upgrade.
What the official Magento docs get wrong
Adobe's official 2.4.9 upgrade guide recommends running composer require magento/product-community-edition:2.4.9 --no-update followed by composer update. The guide does not mention composer why-not at all. Without it, the merchant reads the cryptic Conclusion: don't install output and guesses which package to bump. The guess is wrong roughly half the time, and the upgrade window blows from 30 minutes to 4 hours.
composer why-not is in the official Composer documentation. It is not in Adobe's official Magento upgrade documentation. Read both.When cweagans/composer-patches is the wrong tool
The cweagans patch is a band-aid, not a fix. Use it when:
- The vendor will ship a 2.4.9 release in the next 2-4 weeks and you need to upgrade now.
- The constraint is over-restrictive but the actual code is compatible (most common shape).
- The merchant is on a fixed-price contract with the vendor and waiting for support is contractual.
Do not use it when:
- The vendor module has actually broken on the new dependency (you are patching constraint, not behavior — the runtime will crash).
- The vendor has explicitly dropped support for Magento 2.4.9 (the patch hides a real incompatibility).
- The patch file is more than 50 lines (you are forking, not patching — own the fork or replace the extension).
Pinning the resolver for reproducible builds
Two flags every Magento composer.json should set, regardless of the conflict story:
{
"config": {
"sort-packages": true,
"allow-plugins": {
"cweagans/composer-patches": true,
"magento/composer-dependency-version-audit-plugin": true,
"laminas/laminas-dependency-plugin": true,
"magento/composer-root-update-plugin": true,
"dealerdirect/phpcodesniffer-composer-installer": true
},
"preferred-install": {
"*": "dist"
}
},
"minimum-stability": "stable",
"prefer-stable": true
}The magento/composer-root-update-plugin is what Adobe ships to handle root-level updates of the metapackage. It is enabled by default on new Magento installs but often missing on projects that started life on 2.4.4 or earlier.[4]
The composer.lock conversation
Commit composer.lock. Always. We have lost two production weekends to a merchant who deleted composer.lock on the assumption that "composer will figure it out". It does not — without a lock file, the resolver picks the newest mutually-compatible versions, which is rarely the set you tested on staging.
The corollary: when the conflict workflow above produces a clean install, commit the new lock file immediately. Stage and production must run from the same lock or you are upgrading two different stores.
FAQ
Why does composer keep saying "don't install magento/product-community-edition" with no explanation?
It is explaining — just badly. The Problem 1 block above the conclusion names the actual conflict. Read it. If the block names a package you do not recognize, run composer why-not <that-package> <version-from-the-message>.
Should I delete composer.lock and start fresh?
No. Deletingcomposer.lock turns a 15-minute diagnostic into a 4-hour resolver hunt with no reproducibility. Use composer why-not against the existing lock.Why does cweagans/composer-patches need composer-exit-on-patch-failure?
Without it, a patch that fails to apply (because the upstream composer.json changed format) is treated as a warning and composer continues. You end up with an install that succeeded in CI but is missing the patch in production. composer-exit-on-patch-failure: true makes the install fail loudly.
Can I use composer why-not on Magento Cloud?
Yes — it runs against the same composer.lock. SSH into the build container (magento-cloud ssh) or run it locally against the cloud project's lock file. The output is identical.
What's the difference between composer why and composer why-not?
composer why <package> answers "what installed packages depend on this one". composer why-not <package> <version> answers "why can't this version be installed". Both walk the same dependency graph, opposite directions.[5]
Will Adobe fix these conflicts upstream?
Some of them. The laminas-mail pin was relaxed in a 2.4.9-p1 patch release. The Symfony 7 bump is permanent. The php-amqplib bump is permanent. Plan to handle these classes of conflict on every major Magento release, not just 2.4.9.
What about Magento 2.4.4 to 2.4.8 upgrades?
Same workflow, different package set. Magento 2.4.4 — 2.4.9 upgrades all benefit from composer why-not. The 2.4.6 to 2.4.7 jump trips on elasticsearch/elasticsearch 7.x to 8.x; 2.4.7 to 2.4.8 trips on monolog/monolog 2 to 3; 2.4.8 to 2.4.9 trips on the three shapes above. Same diagnostic, different blockers.
Is there a CI check I can run to catch these before merge?
Yes — add composer validate --strict --no-check-publish and a composer update --dry-run step to your CI pipeline. The first catches malformed composer.json files; the second surfaces resolver conflicts before they reach the production deploy script.
Related reading
- Magento 2.4.9 upgrade — five hidden traps
- Magento upgrade service
- Magento slow checkout — the actual 3 fixes
References
- Composer documentation, why-not (also known as prohibits) command, getcomposer.org/doc/03-cli.md.
- Composer documentation, update / upgrade command, --with-all-dependencies flag, getcomposer.org/doc/03-cli.md.
- Adobe Commerce DevDocs, Upgrade to Magento 2.4.9, experienceleague.adobe.com.
- Magento composer-root-update-plugin, github.com/magento/composer-root-update-plugin.
- Composer documentation, depends (why) and prohibits (why-not) commands, getcomposer.org/doc/03-cli.md.
I run fixed-scope Magento 2.4.4 — 2.4.9 upgrade sprints with a documented composer diagnostic pass, cweagans patch repository, and rollback plan. Fixed quote from $499 audit / $2,499 sprint / ~28h @ $25/hr. See hire me or the Magento upgrade service.