From 9702d8a62045c209f885305cf0742e55b2cded7e Mon Sep 17 00:00:00 2001 From: Ian Flores Siaca <18703558+ian-flores@users.noreply.github.com> Date: Tue, 8 Sep 2026 09:14:56 -0700 Subject: [PATCH 1/2] ci: set an explicit timeout-minutes on every job Only weekly-summary.yml set a timeout, so every other job inherited GitHub's six-hour default. The browser-driven smoke jobs are the real exposure: a wedged Playwright session or a Workbench container that never becomes healthy had no ceiling short of six hours. Values are sized from observed durations on main with headroom -- Workbench smoke 30 minutes against a 14.9 minute worst case, mock-IdP 15 against 6.8, and 5 for the small coordination and status jobs. Closes #633 --- .github/workflows/add-to-team-project.yml | 1 + .github/workflows/ci.yml | 13 +++++++++++++ .github/workflows/connect-integration.yml | 4 ++++ .github/workflows/connect-smoke.yml | 5 +++++ .github/workflows/copilot-setup-steps.yml | 1 + .github/workflows/docker.yml | 1 + .github/workflows/example-report.yml | 3 +++ .github/workflows/install-flow-smoke.yml | 2 ++ .github/workflows/linux-smoke.yml | 3 +++ .github/workflows/mac-smoke.yml | 1 + .github/workflows/mock-idp-e2e.yml | 6 ++++++ .github/workflows/packagemanager-smoke.yml | 5 +++++ .github/workflows/pr-title.yml | 1 + .github/workflows/preview.yml | 2 ++ .github/workflows/publish.yml | 5 +++++ .github/workflows/release.yml | 3 +++ .github/workflows/security-audit.yml | 1 + .github/workflows/website-preview.yml | 2 ++ .github/workflows/website.yml | 3 +++ .github/workflows/workbench-smoke.yml | 6 ++++++ 20 files changed, 68 insertions(+) diff --git a/.github/workflows/add-to-team-project.yml b/.github/workflows/add-to-team-project.yml index 2945d375e..186252ec5 100644 --- a/.github/workflows/add-to-team-project.yml +++ b/.github/workflows/add-to-team-project.yml @@ -13,6 +13,7 @@ permissions: {} jobs: add-to-team-project: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Map label to project URL id: project-url diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e99724a1d..6535e2718 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: # the Selftests Status shim) report success even when the code jobs are skipped. changes: runs-on: ubuntu-latest + timeout-minutes: 5 permissions: contents: read pull-requests: read @@ -46,6 +47,7 @@ jobs: lint: name: Lint & Format runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -68,6 +70,7 @@ jobs: needs: [changes] if: needs.changes.outputs.relevant == 'true' runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -88,6 +91,9 @@ jobs: needs: [changes] if: needs.changes.outputs.relevant == 'true' runs-on: ubuntu-latest + # zizmor downloads its own toolchain via uvx on top of the lint work, so it + # runs longer than the other path-gated jobs here. + timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -127,6 +133,7 @@ jobs: needs: [changes] if: needs.changes.outputs.relevant == 'true' runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -150,6 +157,9 @@ jobs: needs: [changes] if: needs.changes.outputs.relevant == 'true' runs-on: ${{ matrix.os }} + # The heaviest job in this workflow: uv sync plus the full selftest suite, + # across two OSes and two Python versions. + timeout-minutes: 10 strategy: fail-fast: false matrix: @@ -191,6 +201,7 @@ jobs: if: always() needs: [changes, selftest] runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Check selftest matrix result run: | @@ -217,6 +228,7 @@ jobs: github.event.pull_request.user.login != 'dependabot[bot]' && needs.changes.outputs.relevant == 'true' runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -256,6 +268,7 @@ jobs: if: always() needs: [changes, typecheck, actions-lint, audit, lockfile-guard] runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Check CI job results run: | diff --git a/.github/workflows/connect-integration.yml b/.github/workflows/connect-integration.yml index a5a4eace1..f30ab9c52 100644 --- a/.github/workflows/connect-integration.yml +++ b/.github/workflows/connect-integration.yml @@ -35,6 +35,9 @@ jobs: connect-integration: name: Integration tests against Connect ${{ matrix.connect-version }} runs-on: ubuntu-latest + # Measured ~3.7 min per leg (release/preview); this is a drift detector, not + # a merge gate, so a generous ceiling is fine. + timeout-minutes: 15 strategy: fail-fast: false matrix: @@ -195,6 +198,7 @@ jobs: notify-on-failure: name: Notify Slack on failure runs-on: ubuntu-latest + timeout-minutes: 5 needs: connect-integration if: ${{ failure() }} steps: diff --git a/.github/workflows/connect-smoke.yml b/.github/workflows/connect-smoke.yml index fdff46435..fbcc18246 100644 --- a/.github/workflows/connect-smoke.yml +++ b/.github/workflows/connect-smoke.yml @@ -28,6 +28,7 @@ permissions: jobs: changes: runs-on: ubuntu-latest + timeout-minutes: 5 permissions: contents: read pull-requests: read @@ -70,6 +71,7 @@ jobs: # job-rename step below matches the running check by name substring. set-matrix: runs-on: ubuntu-latest + timeout-minutes: 5 permissions: {} outputs: versions: ${{ steps.pick.outputs.versions }} @@ -92,6 +94,8 @@ jobs: || github.event_name == 'workflow_dispatch' name: Smoke test against Connect ${{ matrix.connect-version }} runs-on: ubuntu-latest + # Measured ~3.8 min median. + timeout-minutes: 10 strategy: fail-fast: false matrix: @@ -344,6 +348,7 @@ jobs: if: always() needs: [changes, set-matrix, connect-smoke] runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Check smoke test result run: | diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 18683e4f1..68b05ae3e 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -11,6 +11,7 @@ jobs: # The job MUST be called 'copilot-setup-steps' to be recognized by GitHub Copilot Agent copilot-setup-steps: runs-on: ubuntu-latest + timeout-minutes: 5 # Set minimal permissions for setup steps # Copilot Agent receives its own token with appropriate permissions diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f3ffcab8a..74210f218 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,6 +17,7 @@ env: jobs: build: runs-on: ubuntu-latest + timeout-minutes: 10 permissions: contents: read packages: write diff --git a/.github/workflows/example-report.yml b/.github/workflows/example-report.yml index 8ce2979a5..b15127026 100644 --- a/.github/workflows/example-report.yml +++ b/.github/workflows/example-report.yml @@ -7,6 +7,9 @@ jobs: build: name: Build example report runs-on: ubuntu-latest + # Measured ~8 min: stands up Connect and Workbench containers, runs the + # cross-product example suite, then renders the Quarto report. + timeout-minutes: 20 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/.github/workflows/install-flow-smoke.yml b/.github/workflows/install-flow-smoke.yml index c9aad0ea9..c90250f39 100644 --- a/.github/workflows/install-flow-smoke.yml +++ b/.github/workflows/install-flow-smoke.yml @@ -46,6 +46,7 @@ jobs: ubuntu: name: ubuntu-24.04 (uv tool, root) runs-on: ubuntu-latest + timeout-minutes: 5 # Bare ubuntu:24.04 as root faithfully replays the reported scenario: a full # `vip install` that apt-installs the Chromium system libs itself and then # runs the Playwright step. It is also the only CI job that exercises vip's @@ -84,6 +85,7 @@ jobs: macos: name: macos-latest (uv tool) runs-on: macos-latest + timeout-minutes: 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/.github/workflows/linux-smoke.yml b/.github/workflows/linux-smoke.yml index 268eed4f2..78b2f9471 100644 --- a/.github/workflows/linux-smoke.yml +++ b/.github/workflows/linux-smoke.yml @@ -33,6 +33,9 @@ jobs: smoke: name: ${{ matrix.version }} runs-on: ubuntu-latest + # Builds a Docker image per distro (rhel9/rhel10/opensuse-leap) with system + # package installs; more variable than the plain docker.yml build. + timeout-minutes: 20 strategy: fail-fast: false matrix: diff --git a/.github/workflows/mac-smoke.yml b/.github/workflows/mac-smoke.yml index a0e3706b4..f58d49e49 100644 --- a/.github/workflows/mac-smoke.yml +++ b/.github/workflows/mac-smoke.yml @@ -21,6 +21,7 @@ jobs: smoke: name: macos-latest runs-on: macos-latest + timeout-minutes: 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/.github/workflows/mock-idp-e2e.yml b/.github/workflows/mock-idp-e2e.yml index 351de9e8c..4beb20d1f 100644 --- a/.github/workflows/mock-idp-e2e.yml +++ b/.github/workflows/mock-idp-e2e.yml @@ -44,6 +44,7 @@ concurrency: jobs: changes: runs-on: ubuntu-latest + timeout-minutes: 5 permissions: contents: read pull-requests: read @@ -83,6 +84,7 @@ jobs: # tags when a new Workbench release ships; `release` always tracks newest. set-matrix: runs-on: ubuntu-latest + timeout-minutes: 5 permissions: {} outputs: versions: ${{ steps.pick.outputs.versions }} @@ -106,6 +108,9 @@ jobs: # `!=` first so the release leg yields '' rather than tripping the falsy-`||` trap. name: Mock-IdP E2E (headless-auth)${{ matrix.workbench-version != 'release' && format(' — Workbench {0}', matrix.workbench-version) || '' }} runs-on: ubuntu-latest + # Measured ~5.3 min median, up to 6.8 max (Keycloak + Connect + Workbench + # compose stack plus a real OIDC round-trip). + timeout-minutes: 15 strategy: fail-fast: false matrix: @@ -249,6 +254,7 @@ jobs: if: always() needs: [changes, set-matrix, mock-idp-e2e] runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Check result run: | diff --git a/.github/workflows/packagemanager-smoke.yml b/.github/workflows/packagemanager-smoke.yml index 46d9d7a6c..d4737f839 100644 --- a/.github/workflows/packagemanager-smoke.yml +++ b/.github/workflows/packagemanager-smoke.yml @@ -22,6 +22,7 @@ permissions: jobs: changes: runs-on: ubuntu-latest + timeout-minutes: 5 permissions: contents: read pull-requests: read @@ -63,6 +64,7 @@ jobs: # check by name substring. set-matrix: runs-on: ubuntu-latest + timeout-minutes: 5 permissions: {} outputs: versions: ${{ steps.pick.outputs.versions }} @@ -85,6 +87,8 @@ jobs: || github.event_name == 'workflow_dispatch' name: Smoke test against Package Manager ${{ matrix.pm-version }} runs-on: ubuntu-latest + # Measured ~1.7 min median. + timeout-minutes: 10 strategy: fail-fast: false matrix: @@ -350,6 +354,7 @@ jobs: if: always() needs: [changes, set-matrix, packagemanager-smoke] runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Check smoke test result run: | diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 347b10816..808cd76b1 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -18,6 +18,7 @@ on: jobs: check-title: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Validate PR title uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6 diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 6c031efa0..7e1b5a40c 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -34,6 +34,7 @@ jobs: needs: example-report name: Deploy report preview runs-on: ubuntu-latest + timeout-minutes: 10 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -54,6 +55,7 @@ jobs: if: github.event.action == 'closed' name: Clean up report preview runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5b450cf3b..8dbb7732b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,6 +9,7 @@ jobs: build: name: Build distribution runs-on: ubuntu-latest + timeout-minutes: 15 permissions: contents: read # Attest the artifacts here, in the job that produced them. @@ -56,6 +57,7 @@ jobs: # GitHub release: a mistagged tag fails build and never publishes a release. needs: build runs-on: ubuntu-latest + timeout-minutes: 15 permissions: contents: write id-token: write @@ -145,6 +147,7 @@ jobs: # without its matching release assets. needs: [build, release] runs-on: ubuntu-latest + timeout-minutes: 10 environment: name: pypi url: https://pypi.org/p/posit-vip @@ -164,6 +167,8 @@ jobs: name: Smoke-test published package needs: publish runs-on: ubuntu-latest + # Includes up to 6 retries at 20s apart while waiting for PyPI to index. + timeout-minutes: 10 # Installs from public PyPI and runs the CLI — needs no GITHUB_TOKEN scopes. permissions: {} steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f3df3bc7..da30050ba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,6 +26,9 @@ jobs: release: name: Release runs-on: ubuntu-latest + # Covers the version bump, changelog generation, `just relock`, and the + # commit/tag/push — infrequent (weekly) so a generous ceiling costs nothing. + timeout-minutes: 20 permissions: contents: write concurrency: diff --git a/.github/workflows/security-audit.yml b/.github/workflows/security-audit.yml index 13f59e227..c8e4012ab 100644 --- a/.github/workflows/security-audit.yml +++ b/.github/workflows/security-audit.yml @@ -22,6 +22,7 @@ jobs: audit: name: Full dependency audit runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/.github/workflows/website-preview.yml b/.github/workflows/website-preview.yml index b78ea649e..76dcd3153 100644 --- a/.github/workflows/website-preview.yml +++ b/.github/workflows/website-preview.yml @@ -32,6 +32,7 @@ jobs: needs: example-report name: Build & deploy website preview runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -96,6 +97,7 @@ jobs: if: github.event.action == 'closed' name: Clean up website preview runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 6633a6951..856d2e0d9 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -30,6 +30,9 @@ jobs: deploy: needs: example-report runs-on: ubuntu-latest + # Measured ~8.2 min median, up to 13.4 max (Astro build, presentation + # renders, and the Pages deploy). + timeout-minutes: 20 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: diff --git a/.github/workflows/workbench-smoke.yml b/.github/workflows/workbench-smoke.yml index a64ec9ec0..8534f9964 100644 --- a/.github/workflows/workbench-smoke.yml +++ b/.github/workflows/workbench-smoke.yml @@ -28,6 +28,7 @@ permissions: jobs: changes: runs-on: ubuntu-latest + timeout-minutes: 5 permissions: contents: read pull-requests: read @@ -70,6 +71,7 @@ jobs: # job-rename step below matches the running check by name substring. set-matrix: runs-on: ubuntu-latest + timeout-minutes: 5 permissions: {} outputs: versions: ${{ steps.pick.outputs.versions }} @@ -92,6 +94,9 @@ jobs: || github.event_name == 'workflow_dispatch' name: Smoke test against Workbench ${{ matrix.workbench-version }} runs-on: ubuntu-latest + # Measured ~14.3 min median, up to 14.9 max — the heaviest job in the repo. + # The nightly "full" tier adds ~9 more files on top of the gate set. + timeout-minutes: 30 strategy: fail-fast: false matrix: @@ -444,6 +449,7 @@ jobs: if: always() needs: [changes, set-matrix, workbench-smoke] runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Check smoke test result run: | From 576500b8a99885ba7423939e8a834be268d14bd1 Mon Sep 17 00:00:00 2001 From: Ian Flores Siaca <18703558+ian-flores@users.noreply.github.com> Date: Tue, 8 Sep 2026 09:31:19 -0700 Subject: [PATCH 2/2] fix(ci): realign zizmor ignore line numbers after adding timeouts The artipacked suppressions in .github/zizmor.yml are keyed by file:line:col, and inserting timeout-minutes shifted the checkout steps they point at, so the suppressions stopped matching and CI's enforced artipacked audit failed on website-preview.yml:37 and :102. Realigns all five entries: release.yml 35 to 38, preview.yml 38 to 39 and 58 to 60, website-preview.yml 36 to 37 and 100 to 102. Verified with CI's exact gate: unpinned-uses 0, artipacked 0. --- .github/zizmor.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/zizmor.yml b/.github/zizmor.yml index 3bb4c38bc..7ffbba4fe 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -14,8 +14,8 @@ rules: # - preview.yml -- rossjrw/pr-preview-action pushes to gh-pages # - website-preview.yml -- rossjrw/pr-preview-action pushes to gh-pages ignore: - - release.yml:35:9 - - preview.yml:38:9 - - preview.yml:58:9 - - website-preview.yml:36:9 - - website-preview.yml:100:9 + - release.yml:38:9 + - preview.yml:39:9 + - preview.yml:60:9 + - website-preview.yml:37:9 + - website-preview.yml:102:9