From 7bf9dc4ad3fce1e74ac7020fe3eaf4477aac5d99 Mon Sep 17 00:00:00 2001 From: Eirini Galati Date: Mon, 2 Nov 2026 12:35:58 +0100 Subject: [PATCH 1/8] diag: find the real cause of the PHP 5.4 Windows TLS failure --- .github/workflows/diag.yaml | 85 +++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 .github/workflows/diag.yaml diff --git a/.github/workflows/diag.yaml b/.github/workflows/diag.yaml new file mode 100644 index 0000000..54fe63b --- /dev/null +++ b/.github/workflows/diag.yaml @@ -0,0 +1,85 @@ +--- +name: DIAG win54 TLS + +on: [pull_request] + +permissions: {} + +jobs: + diagnose: + runs-on: windows-latest + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + php-version: ["5.4", "5.6"] + steps: + - uses: shivammathur/setup-php@2.32.0 + with: + php-version: ${{ matrix.php-version }} + tools: composer:2.2 + ini-values: display_errors=On, error_reporting=-1 + + - name: PHP and OpenSSL build info + shell: bash + run: | + php -v + php -r 'echo "OPENSSL_VERSION_TEXT: ", defined("OPENSSL_VERSION_TEXT") ? OPENSSL_VERSION_TEXT : "n/a", PHP_EOL;' + php -r 'echo "curl: "; $v = curl_version(); echo $v["version"], " ssl=", $v["ssl_version"], PHP_EOL;' + + - name: Which stream transports does this PHP have? + shell: bash + run: php -r 'echo implode(", ", stream_get_transports()), PHP_EOL;' + + - name: What does the OS openssl negotiate with packagist? + shell: bash + run: | + openssl version || true + echo | openssl s_client -connect repo.packagist.org:443 -servername repo.packagist.org 2>&1 \ + | grep -E "^\s*(Protocol|Cipher)|SSL handshake|verify return code" || true + + - name: PHP stream TLS attempt, per transport + shell: bash + run: | + cat > t.php <<'PHP' + array( + "verify_peer" => false, "verify_peer_name" => false))); + $fp = @stream_socket_client("$t://repo.packagist.org:443", + $errno, $errstr, 10, STREAM_CLIENT_CONNECT, $ctx); + echo $t, " => ", ($fp ? "CONNECTED" : "FAILED errno=$errno errstr=$errstr"), PHP_EOL; + if ($fp) { fclose($fp); } + PHP + for T in ssl tls sslv3 tlsv1.0 tlsv1.1 tlsv1.2; do + set +e + php t.php "$T" + echo " exit=$?" + set -e + done + + - name: PHP curl attempt (what composer 2.2 actually uses) + shell: bash + run: | + cat > c.php <<'PHP' + &1 | head -25 + echo " composer diagnose exit=$?" From 3eba91604106a550010cdbebdfc5849ee9fff2d7 Mon Sep 17 00:00:00 2001 From: Eirini Galati Date: Mon, 2 Nov 2026 12:38:48 +0100 Subject: [PATCH 2/8] diag: narrow the 5.4 crash to metadata download and parse --- .github/workflows/diag.yaml | 70 ++++++++++++------------------------- 1 file changed, 22 insertions(+), 48 deletions(-) diff --git a/.github/workflows/diag.yaml b/.github/workflows/diag.yaml index 54fe63b..3a895ca 100644 --- a/.github/workflows/diag.yaml +++ b/.github/workflows/diag.yaml @@ -18,68 +18,42 @@ jobs: with: php-version: ${{ matrix.php-version }} tools: composer:2.2 - ini-values: display_errors=On, error_reporting=-1 + ini-values: display_errors=On, error_reporting=-1, memory_limit=-1 - - name: PHP and OpenSSL build info + - name: Runtime shape shell: bash run: | - php -v - php -r 'echo "OPENSSL_VERSION_TEXT: ", defined("OPENSSL_VERSION_TEXT") ? OPENSSL_VERSION_TEXT : "n/a", PHP_EOL;' - php -r 'echo "curl: "; $v = curl_version(); echo $v["version"], " ssl=", $v["ssl_version"], PHP_EOL;' + php -r 'echo "PHP_INT_SIZE=", PHP_INT_SIZE, " (4=32bit, 8=64bit)", PHP_EOL;' + php -r 'echo "memory_limit=", ini_get("memory_limit"), PHP_EOL;' + php -r 'echo "php.ini=", php_ini_loaded_file(), PHP_EOL;' - - name: Which stream transports does this PHP have? - shell: bash - run: php -r 'echo implode(", ", stream_get_transports()), PHP_EOL;' - - - name: What does the OS openssl negotiate with packagist? - shell: bash - run: | - openssl version || true - echo | openssl s_client -connect repo.packagist.org:443 -servername repo.packagist.org 2>&1 \ - | grep -E "^\s*(Protocol|Cipher)|SSL handshake|verify return code" || true - - - name: PHP stream TLS attempt, per transport - shell: bash - run: | - cat > t.php <<'PHP' - array( - "verify_peer" => false, "verify_peer_name" => false))); - $fp = @stream_socket_client("$t://repo.packagist.org:443", - $errno, $errstr, 10, STREAM_CLIENT_CONNECT, $ctx); - echo $t, " => ", ($fp ? "CONNECTED" : "FAILED errno=$errno errstr=$errstr"), PHP_EOL; - if ($fp) { fclose($fp); } - PHP - for T in ssl tls sslv3 tlsv1.0 tlsv1.1 tlsv1.2; do - set +e - php t.php "$T" - echo " exit=$?" - set -e - done - - - name: PHP curl attempt (what composer 2.2 actually uses) + - name: Fetch packages.json with curl and parse it shell: bash run: | - cat > c.php <<'PHP' + cat > f.php <<'PHP' &1 | head -25 - echo " composer diagnose exit=$?" + composer install --no-ansi --no-interaction --no-progress -vvv > out.txt 2> err.txt + echo " composer exit=$?" + echo "----- stdout tail -----"; tail -20 out.txt + echo "----- stderr tail -----"; tail -40 err.txt From c2e5827dbe4504ae06847b81cfa720de17458984 Mon Sep 17 00:00:00 2001 From: Eirini Galati Date: Mon, 2 Nov 2026 12:40:12 +0100 Subject: [PATCH 3/8] diag: restore checkout, which round 2 dropped --- .github/workflows/diag.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/diag.yaml b/.github/workflows/diag.yaml index 3a895ca..53e1f72 100644 --- a/.github/workflows/diag.yaml +++ b/.github/workflows/diag.yaml @@ -20,6 +20,8 @@ jobs: tools: composer:2.2 ini-values: display_errors=On, error_reporting=-1, memory_limit=-1 + - uses: actions/checkout@v3 + - name: Runtime shape shell: bash run: | @@ -49,6 +51,14 @@ jobs: php f.php echo " exit=$?" + - name: Does composer use curl or the streams fallback? + shell: bash + run: | + set +e + composer install --no-ansi --no-interaction --no-progress -vvv 2>&1 \ + | grep -iE "curl|downloader|streams|tls|ssl error|handshake" | head -20 + echo " grep exit=$?" + - name: composer install with stderr merged, full output shell: bash run: | From 3b24db524abb6522995f73469af26947bd662b8f Mon Sep 17 00:00:00 2001 From: Eirini Galati Date: Mon, 2 Nov 2026 12:41:58 +0100 Subject: [PATCH 4/8] diag: isolate memory_limit as the variable --- .github/workflows/diag.yaml | 65 ++++++++++--------------------------- 1 file changed, 18 insertions(+), 47 deletions(-) diff --git a/.github/workflows/diag.yaml b/.github/workflows/diag.yaml index 53e1f72..c594dce 100644 --- a/.github/workflows/diag.yaml +++ b/.github/workflows/diag.yaml @@ -6,64 +6,35 @@ on: [pull_request] permissions: {} jobs: - diagnose: + memory: runs-on: windows-latest timeout-minutes: 20 strategy: fail-fast: false matrix: - php-version: ["5.4", "5.6"] + include: + - label: "A-production-ini-default-memory" + ini: "" + - label: "B-unlimited-memory" + ini: "memory_limit=-1" + - label: "C-512M" + ini: "memory_limit=512M" + name: ${{ matrix.label }} steps: - uses: shivammathur/setup-php@2.32.0 with: - php-version: ${{ matrix.php-version }} + php-version: "5.4" tools: composer:2.2 - ini-values: display_errors=On, error_reporting=-1, memory_limit=-1 - + ini-values: ${{ matrix.ini }} - uses: actions/checkout@v3 - - - name: Runtime shape - shell: bash - run: | - php -r 'echo "PHP_INT_SIZE=", PHP_INT_SIZE, " (4=32bit, 8=64bit)", PHP_EOL;' - php -r 'echo "memory_limit=", ini_get("memory_limit"), PHP_EOL;' - php -r 'echo "php.ini=", php_ini_loaded_file(), PHP_EOL;' - - - name: Fetch packages.json with curl and parse it - shell: bash - run: | - cat > f.php <<'PHP' - &1 \ - | grep -iE "curl|downloader|streams|tls|ssl error|handshake" | head -20 - echo " grep exit=$?" - - - name: composer install with stderr merged, full output + run: php -r 'echo "memory_limit=", ini_get("memory_limit"), PHP_EOL;' + - name: composer install, capture exit code shell: bash run: | set +e - composer install --no-ansi --no-interaction --no-progress -vvv > out.txt 2> err.txt - echo " composer exit=$?" - echo "----- stdout tail -----"; tail -20 out.txt - echo "----- stderr tail -----"; tail -40 err.txt + composer install --no-ansi --no-interaction --no-progress > out.txt 2> err.txt + echo "COMPOSER EXIT=$?" + echo "--- stdout ---"; tail -8 out.txt + echo "--- stderr ---"; tail -8 err.txt From 8b7640a23eb97e892ad9d579ae28f8d923fdf540 Mon Sep 17 00:00:00 2001 From: Eirini Galati Date: Mon, 2 Nov 2026 12:44:12 +0100 Subject: [PATCH 5/8] diag: pwsh vs bash is the remaining difference --- .github/workflows/diag.yaml | 41 +++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/.github/workflows/diag.yaml b/.github/workflows/diag.yaml index c594dce..9bac726 100644 --- a/.github/workflows/diag.yaml +++ b/.github/workflows/diag.yaml @@ -6,35 +6,40 @@ on: [pull_request] permissions: {} jobs: - memory: + shell_test: runs-on: windows-latest timeout-minutes: 20 strategy: fail-fast: false matrix: - include: - - label: "A-production-ini-default-memory" - ini: "" - - label: "B-unlimited-memory" - ini: "memory_limit=-1" - - label: "C-512M" - ini: "memory_limit=512M" - name: ${{ matrix.label }} + php-version: ["5.4", "5.6"] + name: shell-${{ matrix.php-version }} steps: - uses: shivammathur/setup-php@2.32.0 with: - php-version: "5.4" + php-version: ${{ matrix.php-version }} tools: composer:2.2 - ini-values: ${{ matrix.ini }} - uses: actions/checkout@v3 - - name: Report effective memory_limit + + - name: A. composer install under DEFAULT shell (pwsh on Windows) + continue-on-error: true + run: composer install --no-ansi --no-interaction --no-progress + + - name: Clean between attempts shell: bash - run: php -r 'echo "memory_limit=", ini_get("memory_limit"), PHP_EOL;' - - name: composer install, capture exit code + run: rm -rf vendor composer.lock + + - name: B. composer install under bash + continue-on-error: true + shell: bash + run: composer install --no-ansi --no-interaction --no-progress + + - name: C. does composer write to stderr on this PHP? shell: bash run: | + rm -rf vendor composer.lock set +e - composer install --no-ansi --no-interaction --no-progress > out.txt 2> err.txt - echo "COMPOSER EXIT=$?" - echo "--- stdout ---"; tail -8 out.txt - echo "--- stderr ---"; tail -8 err.txt + composer install --no-ansi --no-interaction --no-progress 2> stderr_only.txt 1> stdout_only.txt + echo "exit=$?" + echo "--- stderr bytes: $(wc -c < stderr_only.txt) ---" + cat stderr_only.txt | head -10 From 17fc759376e2e0ce71256deb184278a98410d4c0 Mon Sep 17 00:00:00 2001 From: Eirini Galati Date: Mon, 2 Nov 2026 12:46:40 +0100 Subject: [PATCH 6/8] diag: exact replica vs minus-validate --- .github/workflows/diag.yaml | 53 +++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/.github/workflows/diag.yaml b/.github/workflows/diag.yaml index 9bac726..97f5ec3 100644 --- a/.github/workflows/diag.yaml +++ b/.github/workflows/diag.yaml @@ -6,40 +6,41 @@ on: [pull_request] permissions: {} jobs: - shell_test: - runs-on: windows-latest - timeout-minutes: 20 + # Byte-for-byte the failing job, matrix trimmed to the one cell. + exact: + runs-on: ${{ matrix.os }} + timeout-minutes: 30 strategy: fail-fast: false matrix: - php-version: ["5.4", "5.6"] - name: shell-${{ matrix.php-version }} + php-version: ["5.4"] + os: [windows-latest] + name: exact-replica steps: - uses: shivammathur/setup-php@2.32.0 with: php-version: ${{ matrix.php-version }} - tools: composer:2.2 + tools: ${{ startsWith(matrix.php-version, '5.') && 'composer:2.2' || 'composer:v2' }} - uses: actions/checkout@v3 - - - name: A. composer install under DEFAULT shell (pwsh on Windows) - continue-on-error: true + - name: Validate composer.json and composer.lock + run: composer validate --strict + - name: Install Dependencies run: composer install --no-ansi --no-interaction --no-progress + - name: Run test suite + run: vendor/bin/phpunit - - name: Clean between attempts - shell: bash - run: rm -rf vendor composer.lock - - - name: B. composer install under bash - continue-on-error: true - shell: bash + # Same, minus the validate step. + no_validate: + runs-on: windows-latest + timeout-minutes: 30 + name: no-validate-step + steps: + - uses: shivammathur/setup-php@2.32.0 + with: + php-version: "5.4" + tools: composer:2.2 + - uses: actions/checkout@v3 + - name: Install Dependencies run: composer install --no-ansi --no-interaction --no-progress - - - name: C. does composer write to stderr on this PHP? - shell: bash - run: | - rm -rf vendor composer.lock - set +e - composer install --no-ansi --no-interaction --no-progress 2> stderr_only.txt 1> stdout_only.txt - echo "exit=$?" - echo "--- stderr bytes: $(wc -c < stderr_only.txt) ---" - cat stderr_only.txt | head -10 + - name: Run test suite + run: vendor/bin/phpunit From 12a7b17ec036dca021753e4bcaba55be699d0251 Mon Sep 17 00:00:00 2001 From: Eirini Galati Date: Mon, 2 Nov 2026 12:48:17 +0100 Subject: [PATCH 7/8] diag: full 15-job matrix replica to test contention --- .github/workflows/diag.yaml | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/.github/workflows/diag.yaml b/.github/workflows/diag.yaml index 97f5ec3..b51c708 100644 --- a/.github/workflows/diag.yaml +++ b/.github/workflows/diag.yaml @@ -6,20 +6,26 @@ on: [pull_request] permissions: {} jobs: - # Byte-for-byte the failing job, matrix trimmed to the one cell. - exact: + Unit_tests: runs-on: ${{ matrix.os }} timeout-minutes: 30 strategy: fail-fast: false matrix: - php-version: ["5.4"] - os: [windows-latest] - name: exact-replica + php-version: [ + "5.4", + "5.6", + "7.4", + "8.0", + "8.5", + ] + os: [ubuntu-latest, macOS-latest, windows-latest] steps: - uses: shivammathur/setup-php@2.32.0 with: php-version: ${{ matrix.php-version }} + # composer 2.3 raised its floor to PHP 7.2.5, so the 5.x jobs need + # the 2.2 LTS line. composer:v2 resolves to the newest 2.x. tools: ${{ startsWith(matrix.php-version, '5.') && 'composer:2.2' || 'composer:v2' }} - uses: actions/checkout@v3 - name: Validate composer.json and composer.lock @@ -28,19 +34,3 @@ jobs: run: composer install --no-ansi --no-interaction --no-progress - name: Run test suite run: vendor/bin/phpunit - - # Same, minus the validate step. - no_validate: - runs-on: windows-latest - timeout-minutes: 30 - name: no-validate-step - steps: - - uses: shivammathur/setup-php@2.32.0 - with: - php-version: "5.4" - tools: composer:2.2 - - uses: actions/checkout@v3 - - name: Install Dependencies - run: composer install --no-ansi --no-interaction --no-progress - - name: Run test suite - run: vendor/bin/phpunit From 039363f70b778def21570e1b8cda3ce1e43bfad9 Mon Sep 17 00:00:00 2001 From: Eirini Galati Date: Mon, 2 Nov 2026 13:07:40 +0100 Subject: [PATCH 8/8] diag: bisect composer 2.2.x on PHP 5.4 Windows --- .github/workflows/diag.yaml | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/.github/workflows/diag.yaml b/.github/workflows/diag.yaml index b51c708..2bce034 100644 --- a/.github/workflows/diag.yaml +++ b/.github/workflows/diag.yaml @@ -6,31 +6,27 @@ on: [pull_request] permissions: {} jobs: - Unit_tests: - runs-on: ${{ matrix.os }} - timeout-minutes: 30 + composer_version: + runs-on: windows-latest + timeout-minutes: 20 strategy: fail-fast: false matrix: - php-version: [ - "5.4", - "5.6", - "7.4", - "8.0", - "8.5", - ] - os: [ubuntu-latest, macOS-latest, windows-latest] + composer: ["2.2.9", "2.2.21", "2.2.31"] + name: composer-${{ matrix.composer }} steps: - uses: shivammathur/setup-php@2.32.0 with: - php-version: ${{ matrix.php-version }} - # composer 2.3 raised its floor to PHP 7.2.5, so the 5.x jobs need - # the 2.2 LTS line. composer:v2 resolves to the newest 2.x. - tools: ${{ startsWith(matrix.php-version, '5.') && 'composer:2.2' || 'composer:v2' }} + php-version: "5.4" + tools: composer:${{ matrix.composer }} - uses: actions/checkout@v3 - - name: Validate composer.json and composer.lock - run: composer validate --strict - - name: Install Dependencies + - name: Report composer version + shell: bash + run: composer --version + - name: composer install + continue-on-error: true run: composer install --no-ansi --no-interaction --no-progress - - name: Run test suite - run: vendor/bin/phpunit + - name: Did vendor get created? + shell: bash + run: | + if [ -d vendor ]; then echo "RESULT: vendor present - INSTALL OK"; else echo "RESULT: no vendor - INSTALL FAILED"; fi