Skip to content
Latchkey

Composer Script "@php" Fails - Wrong PHP Binary in CI

In Composer scripts, the @php token expands to the PHP interpreter Composer is running under. It is meant to keep scripts on the same PHP as Composer - but if PATH, a wrapper, or extra flags differ in CI, the command behaves differently than it did locally.

What this error means

A scripts entry like @php bin/console … fails in CI with a different error than running php bin/console … directly, or it runs under an unexpected PHP. The script passes locally where the same php is first on PATH.

composer output
// composer.json
"scripts": {
  "post-install-cmd": [ "@php bin/console cache:clear" ]
}

$ composer install
> @php bin/console cache:clear
PHP Warning: Module 'intl' already loaded ... # @php inherited -d extension flags
Script @php bin/console cache:clear handling the post-install-cmd event
returned with error code 255

Common causes

@php resolves to Composer’s interpreter, not your php

@php uses the PHP that launched Composer, including any -d ini overrides Composer was invoked with. That can differ from a bare php on PATH in CI.

The script itself fails on this runner

@php only proxies the binary; if the underlying command (e.g. bin/console cache:clear) errors due to env/config gaps in CI, the script returns non-zero regardless.

How to fix it

Reproduce the script exactly

Run the script through Composer so it uses the same @php resolution as the failing event.

Terminal
composer run-script post-install-cmd -vvv
# compare with a direct invocation
php bin/console cache:clear

Make the interpreter explicit when needed

If @php picks up unwanted ini flags, call a known interpreter or strip overrides.

composer.json
// composer.json
"scripts": {
  "post-install-cmd": [ "@php -d memory_limit=-1 bin/console cache:clear" ]
}

Fix the underlying command’s CI environment

  1. Provide any env vars/config the command needs (APP_ENV, DB stub).
  2. Confirm required extensions are loaded for the script’s interpreter.
  3. Keep the script idempotent so a clean CI checkout does not break it.

How to prevent it

  • Use @php so scripts run on Composer’s PHP, and assert that PHP early.
  • Provide every env var/config a lifecycle script needs in CI.
  • Test scripts via composer run-script locally before relying on them in CI.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →