You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
proc_close() returns raw signal number, indistinguishable from normal exit code #21292
On non-Windows systems, when a child process opened via proc_open() is killed by a signal, proc_close() returns the raw signal number (e.g. 11 for SIGSEGV) rather than following the Unix convention of 128 + signal (e.g. 139).
This makes it impossible for PHP userland code to distinguish between "process exited normally with code 11" and "process was killed by SIGSEGV".
Underlying cause
Internally, proc_close() calls waitpid() which returns an encoded status. The C macros WIFEXITED()/WEXITSTATUS() and WIFSIGNALED()/WTERMSIG() exist to decode this, but proc_close() does not use them to differentiate the two cases. It effectively passes back a value that loses the signal/exit distinction.
PHP's own pcntl_waitpid() + pcntl_wifexited() + pcntl_wtermsig() handle this correctly, but pcntl is not always available, and proc_close() is the standard way to get the exit status of a proc_open() process.
Reproduction
<?php// Child script that triggers SIGSEGV$child = '<?php posix_kill(posix_getpid(), 11);';
$process = proc_open(
[PHP_BINARY, '-r', $child],
[],
$pipes
);
$exitCode = proc_close($process);
// Expected: 139 (128 + 11, following bash/Unix convention)// Actual: 11 (raw signal number, same as a normal exit code)echo"Exit code: $exitCode\n";
echo"Is this SIGSEGV or a normal exit(11)? Impossible to tell.\n";
Expected behavior
proc_close() should return 128 + signal_number when the child was killed by a signal, consistent with how bash and other Unix shells report signal termination via $?. This would allow userland code to detect crashes.
Alternatively, expose the signal information through a separate mechanism (e.g. an optional by-reference parameter, or a new proc_exit_status() function).
Real-world impact
This affects tools that use proc_open() to spawn child processes, notably composer/xdebug-handler which restarts PHP processes. When the child crashes (e.g. due to a PHP JIT bug triggering SIGSEGV), the parent receives exit code 11 with no way to detect it was a signal — leading to silent, misleading failures in tools like Psalm, PHPStan, and Composer.
Note that proc_get_status() already handles this correctly today — it returns separate signaled and termsig fields. So userland code can work around this right now:
$status = proc_get_status($process);
while ($status['running']) {
usleep(1000);
$status = proc_get_status($process);
}
if ($status['signaled']) {
$exitCode = 128 + $status['termsig'];
} else {
$exitCode = $status['exitcode'];
}
proc_close($process); // free the resource, ignore return value
That said, proc_close() should still be fixed — every user of proc_open shouldn't need to know about this workaround.
Activity
Fix proc_close() to return 128+signal when child is killed by signal
alies-dev commentedon Feb 25, 2026
Note that
proc_get_status()already handles this correctly today — it returns separatesignaledandtermsigfields. So userland code can work around this right now:That said,
proc_close()should still be fixed — every user ofproc_openshouldn't need to know about this workaround.PR: #21293
Add tests proving proc_get_status detects signal deaths
Fix proc_close() to return 128+signal when child is killed by signal
Cache signaled exit status in waitpid_cached(), document phpGH-21292 …