From 38fff6a49198e3992c916edbb439d4a5c155987a Mon Sep 17 00:00:00 2001 From: Joe Fusco Date: Thu, 20 Aug 2026 07:37:46 -0700 Subject: [PATCH] fix: bound every CI job with a timeout and stop masking install failures The Playwright job was the only test job with `timeout-minutes`, so the recent stalled-mirror hang was capped at 30 minutes there but would have run to GitHub's 360-minute default anywhere else. phpunit is the sharper case: it clones Gutenberg trunk, runs npm install and builds wp-env's Docker images -- strictly more unbounded network work than Playwright -- and it's the job that actually failed on the last main run. Give each job a cap sized to a few times its observed runtime. Also drop Playwright's own cap from 30 to 15 minutes. Its browser install is separately bounded at 10 minutes worst case now, so the old number was ~5x a typical run and only delayed the report of a genuine hang. In build-gutenberg.sh, `npm install ... | grep ... || true` swallowed a failing npm alongside the grep it was meant to tolerate (grep exits 1 when it matches nothing), letting a broken install fall through to a confusing error from the build step instead. Check npm's own status via PIPESTATUS so only grep is allowed to fail. --- .github/workflows/ci.yml | 1 + .github/workflows/codeql.yml | 1 + .github/workflows/phpcs.yml | 1 + .github/workflows/phpstan.yml | 1 + .github/workflows/phpunit.yml | 4 ++++ .github/workflows/playwright.yml | 4 +++- .github/workflows/plugin-check.yml | 1 + build-gutenberg.sh | 11 ++++++++++- 8 files changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80bd9d2..66dd21a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,7 @@ jobs: # instead of the "skipped" state it can't distinguish from "never ran". gate: runs-on: ubuntu-24.04 + timeout-minutes: 5 outputs: is_release_please: ${{ steps.check.outputs.result }} steps: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 39283d8..2bb324a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -29,6 +29,7 @@ jobs: analyze: name: Analyze (${{ matrix.language }}) runs-on: ubuntu-24.04 + timeout-minutes: 20 if: ${{ !startsWith(github.head_ref, 'release-please--') }} strategy: fail-fast: false diff --git a/.github/workflows/phpcs.yml b/.github/workflows/phpcs.yml index 135cb67..46b4f05 100644 --- a/.github/workflows/phpcs.yml +++ b/.github/workflows/phpcs.yml @@ -9,6 +9,7 @@ permissions: jobs: phpcs: runs-on: ubuntu-24.04 + timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index d79e54f..6799a27 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -9,6 +9,7 @@ permissions: jobs: phpstan: runs-on: ubuntu-24.04 + timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 9967226..b0f8a45 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -17,6 +17,10 @@ jobs: phpunit: name: PHP ${{ matrix.php }} runs-on: ubuntu-24.04 + # Caps the unbounded network work here (Gutenberg clone, npm install, + # wp-env image builds), which would otherwise run to GitHub's 360-minute + # default -- a stalled mirror has hung this job before. + timeout-minutes: 30 strategy: fail-fast: false matrix: diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index c310a61..2e2310d 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -10,7 +10,9 @@ jobs: playwright: name: Playwright Tests runs-on: ubuntu-24.04 - timeout-minutes: 30 + # The browser install is separately bounded at 10 minutes worst case, so + # this only needs headroom for the Gutenberg build and the test run. + timeout-minutes: 15 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/.github/workflows/plugin-check.yml b/.github/workflows/plugin-check.yml index f91c111..4fb898e 100644 --- a/.github/workflows/plugin-check.yml +++ b/.github/workflows/plugin-check.yml @@ -13,6 +13,7 @@ permissions: jobs: plugin-check: runs-on: ubuntu-24.04 + timeout-minutes: 15 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 if: ${{ !inputs.skip }} diff --git a/build-gutenberg.sh b/build-gutenberg.sh index 0af60f1..6e26ac4 100755 --- a/build-gutenberg.sh +++ b/build-gutenberg.sh @@ -30,7 +30,16 @@ echo "🏗️ Building Gutenberg (this takes ~2 minutes)..." cd "$GUTENBERG_DIR" echo "📦 Installing dependencies..." -npm install --legacy-peer-deps 2>&1 | grep -E "added|removed|changed|^npm" || true +# grep exits 1 when it matches nothing, so read npm's own status from +# PIPESTATUS rather than a trailing `|| true` that would swallow both. +set +e +npm install --legacy-peer-deps 2>&1 | grep -E "added|removed|changed|^npm" +npm_status=${PIPESTATUS[0]} +set -e +if [ "$npm_status" -ne 0 ]; then + echo "❌ npm install failed for Gutenberg trunk (exit $npm_status)" >&2 + exit "$npm_status" +fi echo "🔧 Running build..." npm run build 2>&1 | tail -10