Repository navigation
fix: make the PHP CI matrix green again - #51
Merged
Merged
Conversation
The phpstan job has been red on every branch since April, and the errors kept changing because composer.phpstan.json asked for ^2.1 with its lock file gitignored, so each run resolved whatever was newest. In September it reported two curl_getinfo stub errors at Client.php:156-157; it now reports two entirely different ones. Pinning to an exact version stops the target moving. Against 2.3.4 the two remaining findings are correct, not false positives: 127 Comparison operation ">=" between 0|1 and 0 is always true 199 Unreachable statement - code above always terminates Every path through the Client::request retry loop returns, continues or throws, and a continue only happens while $retries > 0. So $retries is only ever 0 or 1 at the condition check, and the loop never ends by exhausting its bound, which makes the trailing throw dead today. Both constructs are deliberate -- the bound documents the retry budget and the throw guards against a future edit introducing a fall-through path -- so they are recorded in ignoreErrors with that reasoning rather than removed. Worth a maintainer decision if you would rather drop the throw. Verified: phpstan reports no errors on both PHP 7.4 (what CI uses) and 8.3.
Neither failure is about dependency resolution, and neither needs a committed composer.lock. PHP 5.4 resolves and tests fine today on both ubuntu and macOS; only specific runner/OS combinations are broken. macOS, PHP 5.6/7.4/8.0/8.5: killed at exactly 10m04s by timeout-minutes, every time, inside setup-php and before actions/checkout runs. macOS 5.4 finishes in 37s, so the others are compiling rather than fetching a prebuilt binary. Raised the timeout to 30 minutes for both jobs. Windows, PHP 5.4: composer exits 1 roughly a quarter of a second after "Loading composer repositories", printing nothing at all. A resolution failure would explain itself, so this is a crash. It passed in September and nothing in the repo changed, so the runner moved. composer 2.3 raised its floor to PHP 7.2.5 and `tools: composer:v2` installs the newest 2.x, which makes an incompatible composer the most likely cause, so the 5.x jobs now ask for the 2.2 LTS line explicitly. The Windows change is a best guess that CI can confirm or refute quickly. If 5.4 on Windows still crashes, the fallback is to drop that one combination; 5.4 stays genuinely covered by ubuntu and macOS either way.
Reverts the temporary display_errors and composer -vvv changes from the two preceding commits, now that they have answered the question. composer -vvv showed the job reaching Downloading https://repo.packagist.org/packages.json and the process then exiting 1 with no output whatsoever, 0.17s later. Nothing was printed even with display_errors=On and error_reporting=-1, so this is not a suppressed PHP error; the process dies. The CA bundle is located and reported valid immediately beforehand, so it is not certificate discovery either. PHP 5.4.45 on Windows NT 6.2 cannot complete a TLS handshake with packagist and crashes rather than failing. On ubuntu and macOS, PHP 5.4 links the system OpenSSL and passes. Packagist's TLS requirements are not ours to change and the PHP 5.4 Windows binaries are long EOL, so nothing in this repo can fix this combination. It passed in September; the runner image moved, not the repo. PHP 5.4 support is unaffected. It stays in the matrix and stays genuinely tested, on ubuntu and macOS, and composer.json still declares >=5.3.0.
…ners on: [push, pull_request] fired two identical runs for every push to a PR branch. On commit ca20989 they were created three seconds apart, and their ten macOS jobs all started within twenty seconds of each other, each invoking setup-php against the same runner tool cache. The result was that the same commit produced opposite macOS outcomes: pull_request run 5/5 macOS jobs passed push run 5/5 macOS jobs failed The failures were "composer: command not found" at composer validate -- setup-php reported success but left composer off the PATH, which is what a lost race over the tool cache looks like. The earlier macOS timeouts are likely the same contention, ten jobs building PHP at once. Restricting push to master gives PR branches exactly one run, and a concurrency group cancels superseded runs on rapid pushes. Integration_tests was gated on the push event, which previously included PR branches. With push now master-only, the condition is widened to master pushes plus same-repo pull requests, so coverage and API quota use are unchanged. Fork PRs are excluded because they never receive TINIFY_KEY.
Contributor
Author
|
Re-verified the PHP 5.4 / Windows exclusion after the duplicate-run fix. Worth checking, because that cell was diagnosed before the race was found — so the original evidence was gathered while the workflow was competing with itself. Re-enabled it on a throwaway branch with the race fix in place. It still fails, identically:
Independently justified. The exclusion stays, and it is one matrix cell — Windows still covers 5.6, 7.4, 8.0 and 8.5, and PHP 5.4 is still covered on ubuntu and macOS. |
PHP 5.4 on Windows cannot complete a TLS handshake with packagist and
crashes rather than erroring, so composer cannot fetch anything there.
That is a property of the PHP 5.4 Windows build's bundled OpenSSL, not
something this repo can change, and the earlier commit simply dropped the
combination.
It does not have to be dropped. Installing dependencies is only a
download; it does not need to happen on the PHP under test. composer's
config.platform.php tells it to solve the graph as if PHP were a given
version regardless of the runtime, so for this one cell the job now sets
up PHP 8.3, targets 5.4 when resolving, installs, then switches to 5.4
for the test run.
Confirmed on a scratch branch that this tests what it claims to:
php -r 'echo PHP_VERSION;' -> 5.4.45
vendor/bin/phpunit -> PHPUnit 4.8.36
OK (78 tests, 107 assertions)
So the suite runs on a real 5.4 runtime against the 5.4-era phpunit, and
the whole matrix is green at 21/21. A step printing PHP_VERSION is kept
in the job so a future regression to the wrong runtime stays visible.
Scoped to the 5.4/windows cell via CROSS_RESOLVE; no other job changes.
Replaces the cross-version resolve added earlier on this branch, which was built on a wrong diagnosis. The 5.4 Windows failure has nothing to do with TLS, and dependencies do not need resolving under a different PHP. Bisected on windows-latest / PHP 5.4, checking whether vendor/ actually appears rather than trusting the step's exit status: composer 2.2.9 vendor present install OK composer 2.2.21 vendor present install OK composer 2.2.31 no vendor install FAILED composer 2.2.31 was released 2026-10-29. These jobs began failing 2026-10-31 and passed throughout September, so this is a regression inside the 2.2 LTS line rather than anything about this project. `composer:2.2` resolves to the newest 2.2.x, which is how the broken release arrived without any commit landing here. That also explains why it looked like TLS. composer 2.2.31 exits 1 having printed nothing at all, even with display_errors=On and error_reporting=-1, and the last thing it logs under -vvv is the first packagist request. PHP 5.4's streams OpenSSL really is 0.9.8 with no TLS 1.2, which made a TLS story fit -- but composer uses curl, which links OpenSSL 1.0.2u, reaches packagist fine and returns HTTP 200, and `composer diagnose` reports https connectivity OK on that same runtime. Also adds an explicit vendor/ check after install. Several of the probes run while investigating this reported success while installing nothing, and the test step would otherwise be the first thing to notice.
This was referenced Nov 2, 2026
2.2.21 was simply a version I had tested, not the highest working one, and it carries known composer advisories: CVE-2023-43655 (fixed 2.2.22), CVE-2024-24821 (fixed 2.2.23) and CVE-2024-35241/35242 (fixed 2.2.24). Composer here is a CI build tool rather than a shipped dependency, so the exposure is limited, but there is no reason to take it. Bisected the full range on windows-latest / PHP 5.4, checking for vendor/ rather than step exit status: 2.2.22 OK 2.2.23 OK 2.2.24 OK 2.2.25 OK 2.2.26 OK 2.2.27 OK 2.2.28 OK 2.2.29 OK 2.2.30 OK 2.2.31 BROKEN Only the newest release is affected, so 2.2.30 keeps every security fix in the line and avoids the regression. 2.2.31 is currently the newest 2.2.x, so there is no upstream fix to wait for; worth revisiting if a 2.2.32 appears.
rkoopmans
approved these changes
Nov 2, 2026
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.
Makes the PHP CI matrix fully green. Three unrelated breakages, none caused by any commit in this repo — all three are environment drift that landed while the repo sat still.
No
composer.lockis involved, and committing one would not have helped. More on that at the bottom.1. phpstan — pinned
Red since April, and the reported errors kept changing: in September it flagged two
curl_getinfostub errors atClient.php:156-157; it now flags two entirely different ones.composer.phpstan.jsonasked for^2.1with its lock gitignored, so every run resolved whatever was newest. Pinned to an exact version.The two findings that remain under 2.3.4 are correct, not false positives:
Every path through the
Client::requestretry loop returns, continues or throws, and acontinueonly happens while$retries > 0. So$retriesis only ever0or1at the condition check, the loop never ends by exhausting its bound, and the trailingmax retries exceededthrow is dead today. Both look deliberate — the bound documents the retry budget, the throw guards a future fall-through path — so they are recorded inignoreErrorswith that reasoning rather than deleting working retry logic. Say the word if you would rather drop the throw and simplify the loop.2. macOS — the workflow was running twice and racing itself
This was the real cause, and it is fixed.
on: [push, pull_request]fired two complete runs for every push to a PR branch. On commitca20989the two were created three seconds apart, and their ten macOS jobs all started within twenty seconds of each other, each invokingsetup-phpagainst the same runner tool cache:pull_requestpushcomposer: command not foundIdentical commit, identical workflow, opposite outcomes.
setup-phpreported success but left composer off the PATH, which is what losing a race over the tool cache looks like. The 10-minute timeouts seen earlier are very likely the same contention, ten jobs building PHP simultaneously — which is also why raisingtimeout-minutesappeared to help without being the actual fix.Restricting
pushto master gives PR branches exactly one run, and aconcurrencygroup cancels superseded runs on rapid pushes. This halves CI spend per PR as a side effect.Integration_testswas gated ongithub.event_name == 'push', which previously included PR branches. With push now master-only that would have silently dropped integration coverage from PRs, so the condition is widened to master pushes plus same-repo pull requests — coverage and API quota use unchanged. Fork PRs stay excluded because they never receiveTINIFY_KEY.timeout-minutesis still raised from 10 to 30, now as genuine headroom rather than a fix.3. PHP 5.4 on Windows — an upstream composer regression
composer installexits 1 on PHP 5.4 under Windows having printed nothing at all, even withdisplay_errors=Onanderror_reporting=-1, and without creatingvendor/. The last thing it logs under-vvvis its first request to packagist.Bisected on
windows-latest/ PHP 5.4, checking whethervendor/actually appears rather than trusting the step's exit status:vendor/present — install OKvendor/— install FAILEDBisected across the whole range, not just two points: only the newest release is affected.
composer 2.2.31 was released 2026-10-29. These jobs began failing 2026-10-31 and passed throughout September. So this is a regression inside the 2.2 LTS line, not anything about this project —
composer:2.2resolves to the newest 2.2.x, which is how it arrived without any commit landing here.Fixed by pinning
composer:2.2.30on the 5.x jobs — the highest working release. Nothing is excluded from the matrix.Pinning high matters: every composer advisory in this line is patched at 2.2.24 or earlier (CVE-2023-43655 in 2.2.22, CVE-2024-24821 in 2.2.23, CVE-2024-35241/35242 in 2.2.24), so 2.2.30 gives up no security fix while avoiding the regression. The pin applies only to the 5.x jobs; 7.4/8.0/8.5 stay on
composer:v2.2.2.31 is currently the newest 2.2.x, so there is no upstream fix to wait for, and composer 2.3 raised its floor to PHP 7.2.5 so the 2.2 line is the only option for PHP 5.x at all. Worth revisiting if a 2.2.32 ships.
Worth recording, because it is misleading: PHP 5.4's streams OpenSSL really is 0.9.8 with no TLS 1.1/1.2 support, and its transport list is
ssl, sslv3, sslv2, tlswithtls://to packagist failing. That makes a TLS explanation fit the silent death neatly, and an earlier revision of this branch acted on it. It is not the cause: composer uses curl, which links a separate OpenSSL 1.0.2u, reaches packagist and returns HTTP 200, andcomposer diagnosereportshttps connectivity to packagist: OKon that same runtime. Memory, the PowerShell default shell, thecomposer validatestep and matrix contention were each tested and ruled out too.Also added: an explicit
test -d vendorcheck after install. Several probes during this investigation reported success while installing nothing, and the test step would otherwise be the first thing to notice.Upstream: this is a composer bug worth reporting, and the bisect pins it to a single release — 2.2.30 works, 2.2.31 does not.
Result
One run per commit instead of two, and 21/21 green with nothing excluded: 15/15
Unit_tests(every PHP version on all three OSes) and 6/6Integration_tests. PHPStan passes.Why not a committed
composer.lockIt came up, so worth recording. A single lock cannot span 5.4–8.5.
composer installuses a lock verbatim, so whichever PHP version generates it becomes the only one that can install it — a lock built on 8.3 pinsdoctrine/instantiator 2.0.0(requires php^8.1), which then fails on 5.4, 5.6, 7.4 and 8.0. The widephpunit: ^4.8 || … || ^9.3constraint exists precisely so each PHP version resolves its own compatible phpunit, and a lock collapses that to one set. Committing one would have taken this matrix from one broken job to four.Note on history
Two temporary diagnostic commits (
display_errors,composer -vvv) are present and explicitly reverted by the final commit, rather than squashed — a force-push was not available to me. Happy for you to squash on merge.