# composer/xdebug-handler: restarting a PHP CLI process without Xdebug

> The library Composer extracted from its own codebase to escape Xdebug's startup cost. It creates a temporary ini, restarts the process, and hands the exit code back to the parent.

**composer/xdebug-handler** — Restart a CLI process without loading the xdebug extension.

- Repository: https://github.com/composer/xdebug-handler
- Stars: 2,565 · Forks: 33
- Language: PHP
- License: MIT
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/composer-xdebug-handler

## The problem xdebug-handler removes from a CLI run

Xdebug is a PHP extension that developers load for step debugging, coverage and stack traces. When it is loaded, every PHP process pays for it, including processes that never debug anything: Composer installs, test runners, static analysers, code generators. The README frames the project as a way to "Restart a CLI process without loading the Xdebug extension, unless `xdebug.mode=off`", and notes it was "Originally written as part of composer/composer, now extracted and made available as a stand-alone library".

The target audience is therefore the author of a PHP command-line tool, not the developer running one. If your tool is distributed through Packagist and executed on machines you do not control, you cannot ask each user to disable Xdebug by hand. The library handles that decision at runtime, inside your own entry point.

## How the restart actually happens

The mechanism is a temporary ini file. According to the README, the library creates one "from the loaded (and scanned) ini files, with any references to the Xdebug extension commented out", and current ini settings are merged so that most settings made on the command line or by the application survive. The restarted process then reads that file instead of the user's configuration.

The sequence in the README is: `MYAPP_ALLOW_XDEBUG` is set with internal data to flag the restart, the command line and environment are configured for the restart, the application is restarted in a new process, the restart settings are stored in the environment, `MYAPP_ALLOW_XDEBUG` is unset, the application runs and exits, and the main process exits with the exit code from the restarted process. That last step matters: your tool's exit status is preserved for whatever invoked it, including shell scripts and CI steps.

Signal handling is automatic in two cases. If the pcntl extension is loaded, asynchronous signal handling is enabled, `SIGINT` is set to `SIG_IGN` in the parent and restored to `SIG_DFL` in the restarted process unless another handler was set. From PHP 7.4 on Windows, `CTRL+C` and `CTRL+BREAK` are handled in the restarted process and ignored in the parent.

## Installing composer/xdebug-handler and a first check()

The README gives a single installation command through Composer. It requires PHP 7.2.5 minimum, and the README adds that using the latest PHP version is highly recommended.

```bash
$ composer require composer/xdebug-handler
```

The basic usage is three lines. The constructor takes one parameter, `$envPrefix`, which is upper-cased and prepended to two base values to form environment variable names. With the prefix `myapp`, the library uses `MYAPP_ALLOW_XDEBUG` and `MYAPP_ORIGINAL_INIS`.

```php
use Composer\XdebugHandler\XdebugHandler;

$xdebug = new XdebugHandler('myapp');
$xdebug->check();
unset($xdebug);
```

Call `check()` as early as possible in your entry script, before you do real work. If Xdebug is loaded and active, the process restarts and the rest of your script runs in the new process; if it is not, `check()` returns and execution continues normally. Setting `MYAPP_ALLOW_XDEBUG=1` in the environment overrides the automatic restart and lets Xdebug stay loaded, which is what a developer who wants to debug your tool would do.

For status output during development, the README documents `setLogger(LoggerInterface $logger)`, which takes a PSR-3 logger and reports messages at `DEBUG` or `WARNING` level. It must be called before `check()`. The same messages can be produced through the `XDEBUG_HANDLER_DEBUG` environment variable.

## Limitations the README states plainly

The limitations section is short and specific. Extensions set on the command line will not be loaded in the restarted process, because the restart is built from ini files rather than from the original argument list. Ini file locations are reported as per the restart, which is why the library provides `getAllIniFiles()`: the README says to use it instead of `php_ini_loaded_file` and `php_ini_scanned_files`, which "will report the wrong values in a restarted process". PHP sub-processes may be loaded with Xdebug enabled, and the README points to its process configuration section for that case.

That third point is the one that catches people. A restarted parent does not automatically fix the children it spawns. If your tool shells out to `php` for anything, the sub-process inherits whatever environment the parent passes along, and the documentation treats this as something you configure rather than something the library solves for you. `getRestartSettings()` returns the array you need for that: `tmpIni`, `scannedInis`, `scanDir`, `phprc`, `inis` and `skipped`, or null if the process was not restarted.

## Where composer/xdebug-handler is the wrong tool

This library is not a way to run Xdebug selectively. If you want coverage or step debugging for part of a run, the restart is the opposite of what you want, and the documented escape hatch is `MYAPP_ALLOW_XDEBUG=1`. Note also the condition in the project description: the restart happens "unless `xdebug.mode=off`". Under Xdebug 3, a user who has already set `xdebug.mode=off` is not paying the cost the library removes, and the README's own log example shows the corresponding decision line, `No restart (APP_ALLOW_XDEBUG=0) Allowed by xdebug.mode`.

It is also the wrong tool if your entry point is not a CLI script. The whole design assumes a process it can restart and an exit code it can forward. And if you already control the php.ini on every machine where your code runs, you do not need a runtime library to comment out an extension.

## Compared with asking users to disable Xdebug

The obvious alternative is documentation: tell users to run their PHP CLI with `-n` or with a php.ini that omits Xdebug. That approach has a real advantage over this library. Nothing restarts, so nothing is lost: command-line extensions stay loaded, `php_ini_loaded_file` keeps reporting the truth, and sub-processes inherit a clean environment without extra configuration. The cost is that it depends on every user reading the instructions and applying them to every invocation, which is exactly the failure mode Composer hit often enough to extract this library.

A second alternative is a wrapper script or a Makefile target that invokes PHP with the right flags. That works for a team that controls its own tooling, and it fails for a library published to Packagist, where the entry point is the user's command and not yours. The difference in approach is where the decision lives: in the environment, or inside the process. This library puts it inside the process and accepts the ini-reporting and sub-process caveats as the price.

## Maintenance, version 3 and the MIT licence

The repository is not archived and the last push was on 2026-09-01. The most recent release listed is 3.0.5 from 2024-05-06, preceded by 3.0.4 in March 2024 and 3.0.3 in February 2022, so release cadence is slow and the 3.0.5 tag is the version to pin if you want the newest published code. Version 3, per the README, "Removed support for legacy PHP versions and added type declarations", and long term support for version 2 (PHP 5.3.2 to 7.2.4) follows the Composer 2.2 LTS policy. If you still support PHP below 7.2.5, version 3 is not available to you and version 2 is the branch that matters.

The licence is MIT, which is permissive and imposes no copyleft obligation on your own code. That is a statement about the licence text, not legal advice; check it against your own distribution model if you vendor the source. The upgrade cost between 3.0.x releases is small by the shape of the changelog, but the jump from version 2 to version 3 is a platform decision, because it drops the PHP versions version 2 supported.

## Conclusion

Adopt it if you ship a PHP CLI tool that users may run with Xdebug loaded and you cannot ask them to edit their php.ini, since the library handles the restart and reports the original ini locations through getAllIniFiles(). Do not adopt it if you need Xdebug active inside the same process, if your tool relies on command-line extension loading that the restart drops, or if you are targeting PHP versions below 7.2.5. Before relying on it, verify the behaviour of getAllIniFiles() and getSkippedVersion() in your own restarted process, because the README states that php_ini_loaded_file and php_ini_scanned_files report the wrong values after a restart.

## FAQ

### What is composer/xdebug-handler in PHP?

It is a stand-alone library, extracted from composer/composer, that restarts a CLI process without loading the Xdebug extension, unless xdebug.mode=off. You call check() early in your entry script and the library handles the restart and forwards the exit code.

### How do I install composer/xdebug-handler?

Run composer require composer/xdebug-handler. It requires PHP 7.2.5 minimum, and the README recommends using the latest PHP version.

### How do I stop composer/xdebug-handler from restarting my process?

Set the ALLOW_XDEBUG environment variable built from your prefix, for example MYAPP_ALLOW_XDEBUG=1 for the prefix myapp. The README's log example shows the decision line No restart (MYAPP_ALLOW_XDEBUG=1) for this case.

### Why does php_ini_loaded_file report the wrong value after composer/xdebug-handler restarts?

The restart uses a temporary ini file, so ini file locations are reported as per the restart. The README says to call XdebugHandler::getAllIniFiles() instead, which returns the original ini file locations; they are also available in the MYAPP_ORIGINAL_INIS environment variable.

## Sources

- [composer/xdebug-handler on GitHub](https://github.com/composer/xdebug-handler)
- [Issues](https://github.com/composer/xdebug-handler/issues)
- [License: MIT](https://github.com/composer/xdebug-handler/blob/main/LICENSE)
- [README](https://github.com/composer/xdebug-handler/blob/main/README.md)
- [Releases](https://github.com/composer/xdebug-handler/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/composer-xdebug-handler
