proc_close() returns raw signal number, indistinguishable from normal exit code
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 40.4k
- Forks
- 8.1k
- Avg merge
- 2d 13h
- Merged PRs (30d)
- 96
Description
Description
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.
- Related upstream fix: https://github.com/composer/xdebug-handler/pull/162
- Downstream report: https://github.com/vimeo/psalm/issues/11679
PHP Version
Tested on PHP 8.4 and 8.5, but the behavior has existed since proc_open was introduced.
Operating System
Linux / macOS (any non-Windows POSIX system)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Trace proc_close() on non-Windows systems through waitpid() and the WIFEXITED(), WEXITSTATUS(), WIFSIGNALED(), and WTERMSIG() cases described in the issue. Run the provided proc_open() reproduction with a SIGSEGV child and verify that signal termination is distinguishable from a normal exit code while preserving expected behavior on other platforms.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, php
- Domain
- backend, operating-systems
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100