Skip to content

composer very slow on 2.9.6 and 2.9.7; fast on 2.9.5 #12829

@sc0ttdav3y

Description

@sc0ttdav3y

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:

composer -vvv

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No fields configured for Bug.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions