Hi,
My environment is composer on PHP 8.4 on Docker for Mac. Beginning v2.9.6 and continuing on v2.9.7, composer has become extremely slow. It's fast on 2.9.5.
It slows down based on the presence of one or more scripts in combination with the presence of autoload.classmap. Each script registered progressively slows it by around 2 seconds per script. --no-scripts --no-plugins does not avoid the slowdown.
The timing is directly related to the size of the directory defined in classmap x number of scripts registered.
composer.json
Here's a minimal composer.json to reproduce it:
{
"name": "test/test",
"autoload": {
"classmap": [
"modules"
]
},
"scripts": {
"test01": "echo Hi",
"test02": "echo Hi",
"test03": "echo Hi",
"test04": "echo Hi",
"test07": "echo Hi",
"test09": "echo Hi",
"test10": "echo Hi",
"test11": "echo Hi",
"test12": "echo Hi",
"test13": "echo Hi",
"test14": "echo Hi",
"test15": "echo Hi",
"test16": "echo Hi",
"test17": "echo Hi",
"test18": "echo Hi",
"test19": "echo Hi",
"test20": "echo Hi"
}
}
It's in a project where I have a decent number of php files in modules.
- If you remove the entry under
autoload.classmap, it is fast
- Timing is worse for larger
classmap directories, better for smaller ones.
- It gets progressively slower the more scripts you add at the rate of number of scripts x size of classmap directory.
- This example takes about 30 seconds to run "composer" in the CLI, just to show usage.
Timing variation based on version
On 2.9.6, 28 seconds
bash-5.2# time composer >/dev/null
real 0m28.366s
user 0m12.982s
sys 0m4.213s
On 2.9.7, 26 seconds:
bash-5.2# composer self-update 2.9.7
Upgrading to version 2.9.7 (stable channel).
Use composer self-update --rollback to return to version 2.9.6
bash-5.2# time composer >/dev/null
real 0m26.962s
user 0m12.989s
sys 0m4.110s
On 2.9.5, 0.8s
bash-5.2# composer self-update 2.9.5
Upgrading to version 2.9.5 (stable channel).
Use composer self-update --rollback to return to version 2.9.7
bash-5.2# time composer >/dev/null
real 0m0.832s
user 0m0.772s
sys 0m0.137s
bash-5.2#
Diagnostics
Output of composer diagnose:
bash-5.2# composer diagnose
Checking pubkeys:
Tags Public Key Fingerprint: 57815BA2 7E54DC31 7ECC7CC5 573090D0 87719BA6 8F3BB723 4E5D42D0 84A14642
Dev Public Key Fingerprint: 4AC45767 E5EC2265 2F0C1167 CBBB8A2B 0C708369 153E328C AD90147D AFE50952
OK
Checking Composer version: OK
Composer version: 2.9.7
Checking Composer and its dependencies for vulnerabilities: OK
PHP version: 8.4.20
PHP binary path: /opt/bin/php
OpenSSL version: OpenSSL 3.2.2 4 Jun 2024
curl version: 8.17.0 libz 1.2.11 brotli missing zstd missing ssl OpenSSL/3.2.2 HTTP 1.0, 1.1, 2
zip: extension present, unzip present, 7-Zip not available
Active plugins:
Checking composer.json: WARNING
No license specified, it is recommended to do so. For closed-source software you may use "proprietary" as license.
Checking composer.lock: OK
Checking platform settings: OK
Checking git settings: OK git version 2.50.1
Checking http connectivity to packagist: OK
Checking https connectivity to packagist: OK
Checking github.com oauth access: OK does not expire
Checking disk free space: OK
When I run this command:
I get the following output:
On 5.9.7 with scripts present in composer.json (slow):
bash-5.2# composer -vvv
Reading ./composer.json (/opt/crisisworks/composer.json)
Loading auth config from COMPOSER_AUTH
Loading config file ./composer.json (/opt/crisisworks/composer.json)
Loading auth config from COMPOSER_AUTH
Checked CA file /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem: valid
Executing command (CWD): 'git' '--version'
Executing command (/opt/crisisworks): 'git' 'branch' '-a' '--no-color' '--no-abbrev' '-v'
Executing async command (/opt/crisisworks): 'git' 'rev-list' 'main..feature/XXX'
Executing async command (/opt/crisisworks): 'git' 'rev-list' 'develop..feature/XXX'
Executing async command (/opt/crisisworks): 'git' 'rev-list' 'remotes/origin/master..feature/XXX'
Executing async command (/opt/crisisworks): 'git' 'rev-list' 'remotes/origin/main..feature/XXX'
Executing async command (/opt/crisisworks): 'git' 'rev-list' 'remotes/origin/develop..feature/XXX'
Failed to initialize global composer: Composer could not find the config file: /root/.config/composer/composer.json
Reading ./composer.lock (/opt/crisisworks/composer.lock)
Reading /opt/crisisworks/vendor/composer/installed.json
Running 2.9.7 (2026-04-14 13:31:52) with PHP 8.4.20 on Linux / 6.12.76-linuxkit
______
/ ____/___ ____ ___ ____ ____ ________ _____
/ / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
/_/
Composer version 2.9.7 2026-04-14 13:31:52
Usage:
command [options] [arguments]
Options:
-h, --help Display help for the given command. When no command is given display help for the list command
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi|--no-ansi Force (or disable --no-ansi) ANSI output
-n, --no-interaction Do not ask any interactive question
--profile Display timing and memory usage information
--no-plugins Whether to disable plugins.
--no-scripts Skips the execution of all scripts defined in composer.json file.
-d, --working-dir=WORKING-DIR If specified, use the given directory as working directory.
--no-cache Prevent use of the cache
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
Available commands:
about Shows a short information about Composer
archive Creates an archive of this composer package
audit Checks for security vulnerability advisories for installed packages
browse [home] Opens the package's repository URL or homepage in your browser
bump Increases the lower limit of your composer.json requirements to the currently installed versions
check-platform-reqs Check that platform requirements are satisfied
clear-cache [clearcache|cc] Clears composer's internal package cache
completion Dump the shell completion script
config Sets config options
create-project Creates new project from a package into given directory
depends [why] Shows which packages cause the given package to be installed
diagnose Diagnoses the system to identify common errors
dump-autoload [dumpautoload] Dumps the autoloader
exec Executes a vendored binary/script
fund Discover how to help fund the maintenance of your dependencies
global Allows running commands in the global composer dir ($COMPOSER_HOME)
help Display help for a command
init Creates a basic composer.json file in current directory
install [i] Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json
licenses Shows information about licenses of dependencies
list List commands
outdated Shows a list of installed packages that have updates available, including their latest version
prohibits [why-not] Shows which packages prevent the given package from being installed
reinstall Uninstalls and reinstalls the given package names
remove [rm|uninstall] Removes a package from the require or require-dev
repository [repo] Manages repositories
require [r] Adds required packages to your composer.json and installs them
run-script [run] Runs the scripts defined in composer.json
search Searches for packages
self-update [selfupdate] Updates composer.phar to the latest version
show [info] Shows information about packages
status Shows a list of locally modified packages
suggests Shows package suggestions
test01 Runs the test01 script as defined in composer.json
test02 Runs the test02 script as defined in composer.json
test03 Runs the test03 script as defined in composer.json
test04 Runs the test04 script as defined in composer.json
test07 Runs the test07 script as defined in composer.json
test09 Runs the test09 script as defined in composer.json
test10 Runs the test10 script as defined in composer.json
test11 Runs the test11 script as defined in composer.json
test12 Runs the test12 script as defined in composer.json
test13 Runs the test13 script as defined in composer.json
test14 Runs the test14 script as defined in composer.json
test15 Runs the test15 script as defined in composer.json
test16 Runs the test16 script as defined in composer.json
test17 Runs the test17 script as defined in composer.json
test18 Runs the test18 script as defined in composer.json
test19 Runs the test19 script as defined in composer.json
test20 Runs the test20 script as defined in composer.json
update [u|upgrade] Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file
validate Validates a composer.json and composer.lock
On 5.9.7 with no scripts in composer.json (fast):
bash-5.2# composer -vvv
Reading ./composer.json (/opt/crisisworks/composer.json)
Loading auth config from COMPOSER_AUTH
Loading config file ./composer.json (/opt/crisisworks/composer.json)
Loading auth config from COMPOSER_AUTH
Checked CA file /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem: valid
Executing command (CWD): 'git' '--version'
Executing command (/opt/crisisworks): 'git' 'branch' '-a' '--no-color' '--no-abbrev' '-v'
Executing async command (/opt/crisisworks): 'git' 'rev-list' 'main..feature/XXX'
Executing async command (/opt/crisisworks): 'git' 'rev-list' 'develop..feature/XXX'
Executing async command (/opt/crisisworks): 'git' 'rev-list' 'remotes/origin/master..feature/XXX'
Executing async command (/opt/crisisworks): 'git' 'rev-list' 'remotes/origin/main..feature/XXX'
Executing async command (/opt/crisisworks): 'git' 'rev-list' 'remotes/origin/develop..feature/XXX'
Failed to initialize global composer: Composer could not find the config file: /root/.config/composer/composer.json
Reading ./composer.lock (/opt/crisisworks/composer.lock)
Reading /opt/crisisworks/vendor/composer/installed.json
Running 2.9.7 (2026-04-14 13:31:52) with PHP 8.4.20 on Linux / 6.12.76-linuxkit
______
/ ____/___ ____ ___ ____ ____ ________ _____
/ / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
/_/
Composer version 2.9.7 2026-04-14 13:31:52
Usage:
command [options] [arguments]
Options:
-h, --help Display help for the given command. When no command is given display help for the list command
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi|--no-ansi Force (or disable --no-ansi) ANSI output
-n, --no-interaction Do not ask any interactive question
--profile Display timing and memory usage information
--no-plugins Whether to disable plugins.
--no-scripts Skips the execution of all scripts defined in composer.json file.
-d, --working-dir=WORKING-DIR If specified, use the given directory as working directory.
--no-cache Prevent use of the cache
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
Available commands:
about Shows a short information about Composer
archive Creates an archive of this composer package
audit Checks for security vulnerability advisories for installed packages
browse [home] Opens the package's repository URL or homepage in your browser
bump Increases the lower limit of your composer.json requirements to the currently installed versions
check-platform-reqs Check that platform requirements are satisfied
clear-cache [clearcache|cc] Clears composer's internal package cache
completion Dump the shell completion script
config Sets config options
create-project Creates new project from a package into given directory
depends [why] Shows which packages cause the given package to be installed
diagnose Diagnoses the system to identify common errors
dump-autoload [dumpautoload] Dumps the autoloader
exec Executes a vendored binary/script
fund Discover how to help fund the maintenance of your dependencies
global Allows running commands in the global composer dir ($COMPOSER_HOME)
help Display help for a command
init Creates a basic composer.json file in current directory
install [i] Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json
licenses Shows information about licenses of dependencies
list List commands
outdated Shows a list of installed packages that have updates available, including their latest version
prohibits [why-not] Shows which packages prevent the given package from being installed
reinstall Uninstalls and reinstalls the given package names
remove [rm|uninstall] Removes a package from the require or require-dev
repository [repo] Manages repositories
require [r] Adds required packages to your composer.json and installs them
run-script [run] Runs the scripts defined in composer.json
search Searches for packages
self-update [selfupdate] Updates composer.phar to the latest version
show [info] Shows information about packages
status Shows a list of locally modified packages
suggests Shows package suggestions
update [u|upgrade] Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file
validate Validates a composer.json and composer.lock
bash-5.2#
And on 2.9.6 with script in place but the 'modules' classmap removed, it's fast.
bash-5.2# composer -vvv
Reading ./composer.json (/opt/crisisworks/composer.json)
Loading auth config from COMPOSER_AUTH
Loading config file ./composer.json (/opt/crisisworks/composer.json)
Loading auth config from COMPOSER_AUTH
Checked CA file /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem: valid
Executing command (CWD): 'git' '--version'
Executing command (/opt/crisisworks): 'git' 'branch' '-a' '--no-color' '--no-abbrev' '-v'
Executing async command (/opt/crisisworks): 'git' 'rev-list' 'main..feature/scott-bref3'
Executing async command (/opt/crisisworks): 'git' 'rev-list' 'develop..feature/scott-bref3'
Executing async command (/opt/crisisworks): 'git' 'rev-list' 'remotes/origin/master..feature/scott-bref3'
Executing async command (/opt/crisisworks): 'git' 'rev-list' 'remotes/origin/main..feature/scott-bref3'
Executing async command (/opt/crisisworks): 'git' 'rev-list' 'remotes/origin/develop..feature/scott-bref3'
Failed to initialize global composer: Composer could not find the config file: /root/.config/composer/composer.json
Reading ./composer.lock (/opt/crisisworks/composer.lock)
Reading /opt/crisisworks/vendor/composer/installed.json
Running 2.9.7 (2026-04-14 13:31:52) with PHP 8.4.20 on Linux / 6.12.76-linuxkit
______
/ ____/___ ____ ___ ____ ____ ________ _____
/ / / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__ ) __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
/_/
Composer version 2.9.7 2026-04-14 13:31:52
Usage:
command [options] [arguments]
Options:
-h, --help Display help for the given command. When no command is given display help for the list command
-q, --quiet Do not output any message
-V, --version Display this application version
--ansi|--no-ansi Force (or disable --no-ansi) ANSI output
-n, --no-interaction Do not ask any interactive question
--profile Display timing and memory usage information
--no-plugins Whether to disable plugins.
--no-scripts Skips the execution of all scripts defined in composer.json file.
-d, --working-dir=WORKING-DIR If specified, use the given directory as working directory.
--no-cache Prevent use of the cache
-v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
Available commands:
about Shows a short information about Composer
archive Creates an archive of this composer package
audit Checks for security vulnerability advisories for installed packages
browse [home] Opens the package's repository URL or homepage in your browser
bump Increases the lower limit of your composer.json requirements to the currently installed versions
check-platform-reqs Check that platform requirements are satisfied
clear-cache [clearcache|cc] Clears composer's internal package cache
completion Dump the shell completion script
config Sets config options
create-project Creates new project from a package into given directory
depends [why] Shows which packages cause the given package to be installed
diagnose Diagnoses the system to identify common errors
dump-autoload [dumpautoload] Dumps the autoloader
exec Executes a vendored binary/script
fund Discover how to help fund the maintenance of your dependencies
global Allows running commands in the global composer dir ($COMPOSER_HOME)
help Display help for a command
init Creates a basic composer.json file in current directory
install [i] Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json
licenses Shows information about licenses of dependencies
list List commands
outdated Shows a list of installed packages that have updates available, including their latest version
prohibits [why-not] Shows which packages prevent the given package from being installed
reinstall Uninstalls and reinstalls the given package names
remove [rm|uninstall] Removes a package from the require or require-dev
repository [repo] Manages repositories
require [r] Adds required packages to your composer.json and installs them
run-script [run] Runs the scripts defined in composer.json
search Searches for packages
self-update [selfupdate] Updates composer.phar to the latest version
show [info] Shows information about packages
status Shows a list of locally modified packages
suggests Shows package suggestions
test01 Runs the test01 script as defined in composer.json
test02 Runs the test02 script as defined in composer.json
test03 Runs the test03 script as defined in composer.json
test04 Runs the test04 script as defined in composer.json
test07 Runs the test07 script as defined in composer.json
test09 Runs the test09 script as defined in composer.json
test10 Runs the test10 script as defined in composer.json
test11 Runs the test11 script as defined in composer.json
test12 Runs the test12 script as defined in composer.json
test13 Runs the test13 script as defined in composer.json
test14 Runs the test14 script as defined in composer.json
test15 Runs the test15 script as defined in composer.json
test16 Runs the test16 script as defined in composer.json
test17 Runs the test17 script as defined in composer.json
test18 Runs the test18 script as defined in composer.json
test19 Runs the test19 script as defined in composer.json
test20 Runs the test20 script as defined in composer.json
update [u|upgrade] Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file
validate Validates a composer.json and composer.lock
bash-5.2#
Summary
This is a long report. Thanks for reading to the end.
In summary:
- Composer is slow on 2.9.6+ based on the presence of both
scripts and autoload.classmap pointing to a large project.
- It's progressively slower the more scripts it has
My theory: it looks to me as if something is re-scanning the classmap filesystem for each script registered, leading to a progressively larger timing.
Workaround
Locking composer to 2.9.5 works around this problem for my team, however I note there are two CVEs with that release so it's not a long term solution.
Finally...
Thank you composer team, your ongoing efforts with this project are amazing and are greatly appreciated by all PHP developers worldwide. Amazing stuff.
Hi,
My environment is composer on PHP 8.4 on Docker for Mac. Beginning v2.9.6 and continuing on v2.9.7, composer has become extremely slow. It's fast on 2.9.5.
It slows down based on the presence of one or more
scriptsin combination with the presence ofautoload.classmap. Each script registered progressively slows it by around 2 seconds per script.--no-scripts --no-pluginsdoes not avoid the slowdown.The timing is directly related to the size of the directory defined in classmap x number of scripts registered.
composer.json
Here's a minimal composer.json to reproduce it:
{ "name": "test/test", "autoload": { "classmap": [ "modules" ] }, "scripts": { "test01": "echo Hi", "test02": "echo Hi", "test03": "echo Hi", "test04": "echo Hi", "test07": "echo Hi", "test09": "echo Hi", "test10": "echo Hi", "test11": "echo Hi", "test12": "echo Hi", "test13": "echo Hi", "test14": "echo Hi", "test15": "echo Hi", "test16": "echo Hi", "test17": "echo Hi", "test18": "echo Hi", "test19": "echo Hi", "test20": "echo Hi" } }It's in a project where I have a decent number of php files in
modules.autoload.classmap, it is fastclassmapdirectories, better for smaller ones.Timing variation based on version
On 2.9.6, 28 seconds
On 2.9.7, 26 seconds:
On 2.9.5, 0.8s
Diagnostics
Output of
composer diagnose:When I run this command:
I get the following output:
On 5.9.7 with scripts present in composer.json (slow):
On 5.9.7 with no scripts in composer.json (fast):
And on 2.9.6 with script in place but the 'modules' classmap removed, it's fast.
Summary
This is a long report. Thanks for reading to the end.
In summary:
scriptsandautoload.classmappointing to a large project.My theory: it looks to me as if something is re-scanning the classmap filesystem for each script registered, leading to a progressively larger timing.
Workaround
Locking composer to 2.9.5 works around this problem for my team, however I note there are two CVEs with that release so it's not a long term solution.
Finally...
Thank you composer team, your ongoing efforts with this project are amazing and are greatly appreciated by all PHP developers worldwide. Amazing stuff.