sebastianbergmann/phpunit · error · PHPUnit\Util\PHP\PhpProcessException
Unable to spawn worker process
Error message
Unable to spawn worker process
What it means
JobRunner::startProcess() spawns the child PHP process with proc_open(); when proc_open() does not return a resource it throws PhpProcessException('Unable to spawn worker process'). The job code was already written and the pipes prepared — only the actual process creation failed. Typical causes are an invalid or non-executable PHP binary, proc_open being disabled, or OS-level resource limits.
Source
Thrown at src/Util/PHP/JobRunner.php:223
} else {
$pipeSpec = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
}
$process = proc_open(
$this->buildCommand($job, $temporaryFile),
$pipeSpec,
$pipes,
null,
$environmentVariables,
);
if (!is_resource($process)) {
// @codeCoverageIgnoreStart
throw new PhpProcessException(
'Unable to spawn worker process',
);
// @codeCoverageIgnoreEnd
}
Facade::emitter()->childProcessStarted($job->reason());
return new RunningJob($process, $pipes, $mergedOutputStream, $temporaryFile);
}
/**
* @return non-empty-list<string>
*/
private function buildCommand(Job $job, ?string $file): array
{
$runtime = new Runtime;
$command = [PHP_BINARY];
$phpSettings = $job->phpSettings();View on GitHub (pinned to f123cdb2a2)
Solutions
- Confirm the same PHP binary works where PHPUnit runs: `php -v` and `ls -l $(which php)`; fix PATH or install php-cli
- Check `php -i | grep disable_functions` for proc_open and have it removed from disable_functions (or switch to a host that allows it)
- Raise pid/fork limits (ulimit -u, container pids limit) or reduce parallel child usage
- If the sandbox forbids spawning processes, drop process isolation for those tests
Example fix
# before
$ php -r 'var_dump(function_exists("proc_open"));' # false
$ vendor/bin/phpunit --process-isolation
# PhpProcessException: Unable to spawn worker process
# after: enable proc_open (remove it from disable_functions in php.ini), then
$ vendor/bin/phpunit --process-isolation Defensive patterns
Strategy: try-catch
Validate before calling
if (!function_exists('proc_open')) {
throw new RuntimeException('proc_open is disabled; process isolation unavailable');
}
if (PHP_BINARY === false || !is_executable(PHP_BINARY)) {
throw new RuntimeException('PHP CLI binary missing or not executable');
} Try / catch
use PHPUnit\Util\PHP\PhpProcessException;
try {
$runner->start($job);
} catch (PhpProcessException $e) {
if ($e->getMessage() === 'Unable to spawn worker process') {
// report environment problem; do not retry blindly
throw new RuntimeException('Check PHP_BINARY, proc_open and pid limits', 0, $e);
}
throw $e;
} Prevention
- Prefer `php -v` checks in the same image that runs PHPUnit to validate PHP_BINARY early
- Keep proc_open off disable_functions in CI images; document it as a hard requirement for process isolation
- Raise cgroup pids limits / ulimit -u when running many parallel child processes
- When the sandbox cannot spawn processes, mark those tests to run without isolation
When it happens
Trigger: Running tests in separate processes when the PHP CLI binary used for children does not exist or is not executable, proc_open is listed in disable_functions, the process/fork limit (ulimit, cgroups PIDs controller) is hit, or a container seccomp/apparmor profile blocks process creation.
Common situations: Docker/production-derived images where the php binary lives at a different path than PHP_BINARY reports; shared hosting with proc_open disabled for security; CI containers with a low pids limit; memory exhaustion preventing fork.
Related errors
- Unable to write temporary file
- Unable to create temporary file for redirected output
- getrusage() failed.
- getrusage() did not return the expected keys "%s" and "%s".
- getrusage() returned non-integer values for "%s" and/or "%s"
AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23).
Data as JSON: /api/errors/b6be42ad6725c5de.
Report an issue: GitHub.