fix: terminalize php.wasm runtime rejections for every command - #2271
Merged
Conversation
A php.wasm trap is a property of the runtime, not of the command that happened to be running. It leaves the interpreter unusable, and the rejection arrives out of band on the process rather than through the command's own promise, so the command hangs until something else times it out. 851f0aa wired terminalizeOnPhpWasmRuntimeRejection to wordpress.phpunit only, which left every other command able to hang forever on the identical fault. A trap raised during wordpress.run-php wedged a recipe-run for its whole 1500s budget, four shards deep, and surfaced only as a timeout — the step ledger showed the command completed with exit 0 while the run never returned: at php.wasm._php_stream_write_filtered at php.wasm.mysqlnd_stream_array_from_fd_set at php.wasm.zif_mysqli_poll Apply the terminalizer to every execution. The guard is already narrow where it matters: isPhpWasmRuntimeRejection only claims a RuntimeError whose stack names php.wasm, so nothing else is intercepted, and non-matching rejections keep their existing rethrow path. Add a regression test covering a non-PHPUnit command, and run both rejection tests in CI — the phpunit one was an unwired npm script, so the guard it protects had no gate.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A php.wasm trap is a property of the runtime, not of the command that happened to be running. It leaves the interpreter unusable, and the rejection arrives out of band on the process rather than through the command's own promise — so the command hangs until something else times it out.
851f0aa("Fix PHPUnit WASM runtime rejection handling") introducedterminalizeOnPhpWasmRuntimeRejectionand wired it to a single command:Every other command was left able to hang forever on the identical fault.
Evidence
Hit in the field on
wordpress.run-php. The recipe-run step ledger recorded the command as completed with exit 0 while the run never returned, and the whole 1500s budget was consumed — four test shards deep, ~24 minutes each:Same
zif_mysqli_pollcall site the original fix used in its own test fixture — the fault reappeared through a different command.Change
Apply the terminalizer to every execution.
The guard is already narrow where it matters:
isPhpWasmRuntimeRejectiononly claims aRuntimeErrorwhose stack namesphp.wasm, so nothing else is intercepted, and non-matching rejections keep their existing rethrow path. Widening the call site does not widen what gets caught — it only stops the interception from depending on which command was in flight.Tests
tests/php-wasm-runtime-rejection-any-command.test.ts— awordpress.run-phpstep raising the observed trap must fail terminally in under 500ms, carrywp-codebox-php-wasm-runtime-rejection/infrastructure-failure, and not be reported as a timeout.Verified red before the change: it fails with "php.wasm runtime rejection did not terminalize a non-PHPUnit command within 500ms" — i.e. it hangs, exactly as the field failure did.
Both rejection tests are now wired into
agent-task-contracts.yml.test:phpunit-runtime-rejectionwas an npm script no workflow ran, so the guard it protects had no gate — which is part of why the scoping gap went unnoticed.Verification
Note on the underlying trap
This makes the failure terminal and correctly classified; it does not make
mysqli_pollwork under PHP-WASM. That trap is still worth chasing separately — but it should cost a runtime rejection, not a silent hang for the full budget.