From 80778c2a791e3cc87efe74a013226377d10268c2 Mon Sep 17 00:00:00 2001 From: Tamara Dowis Date: Mon, 20 Jul 2026 23:42:26 -0500 Subject: [PATCH 1/2] fix: check-and-restart job was silently skipping every time (implicit success()) Confirmed across every real failure/cancellation govbot-data has hit so far (3 KY failures, 1 LA timeout-cancellation that ran the full 5h55m) -- check-and-restart showed conclusion "skipped" 100% of the time, meaning this safety net has never once actually fired since it was written. Root cause: GitHub Actions implicitly ANDs a job's if: with success() unless the condition explicitly calls a status-check function (always/ success/failure/cancelled). needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure' doesn't call one, so GitHub silently required the needed job to have succeeded too -- which by definition it hadn't, in exactly the cases this job exists to handle. Fix: if: always() && (needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure'). Note: PAT_WORKFLOW_TRIGGER (needed for the "Trigger workflow restart" step's gh workflow run call) is not currently set as a repo secret on at least govbot-data/sd-legislation (checked directly, total_count: 0) -- org-level couldn't be checked without elevated gh auth scope. Even with this fix, the restart step itself won't work until that secret exists. Flagging for follow-up, not fixed here. Co-Authored-By: Claude Sonnet 5 --- .../.github/workflows/extract-text.yml | 12 ++++++++++-- .../.github/workflows/extract-text.yml | 12 ++++++++++-- .../.github/workflows/extract-text.yml | 12 ++++++++++-- .../.github/workflows/extract-text.yml | 12 ++++++++++-- .../.github/workflows/extract-text.yml | 12 ++++++++++-- .../.github/workflows/extract-text.yml | 12 ++++++++++-- .../.github/workflows/extract-text.yml | 12 ++++++++++-- .../.github/workflows/extract-text.yml | 12 ++++++++++-- .../.github/workflows/extract-text.yml | 12 ++++++++++-- .../.github/workflows/extract-text.yml | 12 ++++++++++-- .../.github/workflows/extract-text.yml | 12 ++++++++++-- 11 files changed, 110 insertions(+), 22 deletions(-) diff --git a/actions/pipeline-manager/__snapshots__/chn-openstates-files/ak-legislation/.github/workflows/extract-text.yml b/actions/pipeline-manager/__snapshots__/chn-openstates-files/ak-legislation/.github/workflows/extract-text.yml index 7628a04..5d308ae 100644 --- a/actions/pipeline-manager/__snapshots__/chn-openstates-files/ak-legislation/.github/workflows/extract-text.yml +++ b/actions/pipeline-manager/__snapshots__/chn-openstates-files/ak-legislation/.github/workflows/extract-text.yml @@ -66,12 +66,20 @@ jobs: echo "⚠️ Summary file not found" fi - # Auto-restart job if extraction was cancelled (timeout) or failed + # Auto-restart job if extraction was cancelled (timeout) or failed. + # + # The `always() &&` is load-bearing, not decoration: GitHub Actions + # implicitly ANDs a job's `if:` with success() unless the condition + # explicitly calls a status-check function (always/success/failure/ + # cancelled). Without it, this job silently skipped every single time -- + # confirmed across every real failure/cancellation this repo has hit so + # far (it never once ran) -- because the needed job having failed made + # the implicit success() false regardless of the explicit check below. check-and-restart: name: Check Status & Restart if Needed needs: extract-text runs-on: ubuntu-latest - if: needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure' + if: always() && (needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure') permissions: actions: write contents: read diff --git a/actions/pipeline-manager/__snapshots__/chn-openstates-files/id-legislation/.github/workflows/extract-text.yml b/actions/pipeline-manager/__snapshots__/chn-openstates-files/id-legislation/.github/workflows/extract-text.yml index dc63c30..f2a6953 100644 --- a/actions/pipeline-manager/__snapshots__/chn-openstates-files/id-legislation/.github/workflows/extract-text.yml +++ b/actions/pipeline-manager/__snapshots__/chn-openstates-files/id-legislation/.github/workflows/extract-text.yml @@ -66,12 +66,20 @@ jobs: echo "⚠️ Summary file not found" fi - # Auto-restart job if extraction was cancelled (timeout) or failed + # Auto-restart job if extraction was cancelled (timeout) or failed. + # + # The `always() &&` is load-bearing, not decoration: GitHub Actions + # implicitly ANDs a job's `if:` with success() unless the condition + # explicitly calls a status-check function (always/success/failure/ + # cancelled). Without it, this job silently skipped every single time -- + # confirmed across every real failure/cancellation this repo has hit so + # far (it never once ran) -- because the needed job having failed made + # the implicit success() false regardless of the explicit check below. check-and-restart: name: Check Status & Restart if Needed needs: extract-text runs-on: ubuntu-latest - if: needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure' + if: always() && (needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure') permissions: actions: write contents: read diff --git a/actions/pipeline-manager/__snapshots__/chn-openstates-files/mt-legislation/.github/workflows/extract-text.yml b/actions/pipeline-manager/__snapshots__/chn-openstates-files/mt-legislation/.github/workflows/extract-text.yml index 2b09250..c0934ff 100644 --- a/actions/pipeline-manager/__snapshots__/chn-openstates-files/mt-legislation/.github/workflows/extract-text.yml +++ b/actions/pipeline-manager/__snapshots__/chn-openstates-files/mt-legislation/.github/workflows/extract-text.yml @@ -66,12 +66,20 @@ jobs: echo "⚠️ Summary file not found" fi - # Auto-restart job if extraction was cancelled (timeout) or failed + # Auto-restart job if extraction was cancelled (timeout) or failed. + # + # The `always() &&` is load-bearing, not decoration: GitHub Actions + # implicitly ANDs a job's `if:` with success() unless the condition + # explicitly calls a status-check function (always/success/failure/ + # cancelled). Without it, this job silently skipped every single time -- + # confirmed across every real failure/cancellation this repo has hit so + # far (it never once ran) -- because the needed job having failed made + # the implicit success() false regardless of the explicit check below. check-and-restart: name: Check Status & Restart if Needed needs: extract-text runs-on: ubuntu-latest - if: needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure' + if: always() && (needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure') permissions: actions: write contents: read diff --git a/actions/pipeline-manager/__snapshots__/chn-openstates-files/pr-legislation/.github/workflows/extract-text.yml b/actions/pipeline-manager/__snapshots__/chn-openstates-files/pr-legislation/.github/workflows/extract-text.yml index 374b03a..c56e567 100644 --- a/actions/pipeline-manager/__snapshots__/chn-openstates-files/pr-legislation/.github/workflows/extract-text.yml +++ b/actions/pipeline-manager/__snapshots__/chn-openstates-files/pr-legislation/.github/workflows/extract-text.yml @@ -66,12 +66,20 @@ jobs: echo "⚠️ Summary file not found" fi - # Auto-restart job if extraction was cancelled (timeout) or failed + # Auto-restart job if extraction was cancelled (timeout) or failed. + # + # The `always() &&` is load-bearing, not decoration: GitHub Actions + # implicitly ANDs a job's `if:` with success() unless the condition + # explicitly calls a status-check function (always/success/failure/ + # cancelled). Without it, this job silently skipped every single time -- + # confirmed across every real failure/cancellation this repo has hit so + # far (it never once ran) -- because the needed job having failed made + # the implicit success() false regardless of the explicit check below. check-and-restart: name: Check Status & Restart if Needed needs: extract-text runs-on: ubuntu-latest - if: needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure' + if: always() && (needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure') permissions: actions: write contents: read diff --git a/actions/pipeline-manager/__snapshots__/chn-openstates-files/wy-legislation/.github/workflows/extract-text.yml b/actions/pipeline-manager/__snapshots__/chn-openstates-files/wy-legislation/.github/workflows/extract-text.yml index 32b32bd..883de5b 100644 --- a/actions/pipeline-manager/__snapshots__/chn-openstates-files/wy-legislation/.github/workflows/extract-text.yml +++ b/actions/pipeline-manager/__snapshots__/chn-openstates-files/wy-legislation/.github/workflows/extract-text.yml @@ -66,12 +66,20 @@ jobs: echo "⚠️ Summary file not found" fi - # Auto-restart job if extraction was cancelled (timeout) or failed + # Auto-restart job if extraction was cancelled (timeout) or failed. + # + # The `always() &&` is load-bearing, not decoration: GitHub Actions + # implicitly ANDs a job's `if:` with success() unless the condition + # explicitly calls a status-check function (always/success/failure/ + # cancelled). Without it, this job silently skipped every single time -- + # confirmed across every real failure/cancellation this repo has hit so + # far (it never once ran) -- because the needed job having failed made + # the implicit success() false regardless of the explicit check below. check-and-restart: name: Check Status & Restart if Needed needs: extract-text runs-on: ubuntu-latest - if: needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure' + if: always() && (needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure') permissions: actions: write contents: read diff --git a/actions/pipeline-manager/__snapshots__/chn-openstates-test/ak-legislation/.github/workflows/extract-text.yml b/actions/pipeline-manager/__snapshots__/chn-openstates-test/ak-legislation/.github/workflows/extract-text.yml index 56be5aa..5e35bf9 100644 --- a/actions/pipeline-manager/__snapshots__/chn-openstates-test/ak-legislation/.github/workflows/extract-text.yml +++ b/actions/pipeline-manager/__snapshots__/chn-openstates-test/ak-legislation/.github/workflows/extract-text.yml @@ -66,12 +66,20 @@ jobs: echo "⚠️ Summary file not found" fi - # Auto-restart job if extraction was cancelled (timeout) or failed + # Auto-restart job if extraction was cancelled (timeout) or failed. + # + # The `always() &&` is load-bearing, not decoration: GitHub Actions + # implicitly ANDs a job's `if:` with success() unless the condition + # explicitly calls a status-check function (always/success/failure/ + # cancelled). Without it, this job silently skipped every single time -- + # confirmed across every real failure/cancellation this repo has hit so + # far (it never once ran) -- because the needed job having failed made + # the implicit success() false regardless of the explicit check below. check-and-restart: name: Check Status & Restart if Needed needs: extract-text runs-on: ubuntu-latest - if: needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure' + if: always() && (needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure') permissions: actions: write contents: read diff --git a/actions/pipeline-manager/__snapshots__/chn-openstates-test/id-legislation/.github/workflows/extract-text.yml b/actions/pipeline-manager/__snapshots__/chn-openstates-test/id-legislation/.github/workflows/extract-text.yml index 3486530..cf272bb 100644 --- a/actions/pipeline-manager/__snapshots__/chn-openstates-test/id-legislation/.github/workflows/extract-text.yml +++ b/actions/pipeline-manager/__snapshots__/chn-openstates-test/id-legislation/.github/workflows/extract-text.yml @@ -66,12 +66,20 @@ jobs: echo "⚠️ Summary file not found" fi - # Auto-restart job if extraction was cancelled (timeout) or failed + # Auto-restart job if extraction was cancelled (timeout) or failed. + # + # The `always() &&` is load-bearing, not decoration: GitHub Actions + # implicitly ANDs a job's `if:` with success() unless the condition + # explicitly calls a status-check function (always/success/failure/ + # cancelled). Without it, this job silently skipped every single time -- + # confirmed across every real failure/cancellation this repo has hit so + # far (it never once ran) -- because the needed job having failed made + # the implicit success() false regardless of the explicit check below. check-and-restart: name: Check Status & Restart if Needed needs: extract-text runs-on: ubuntu-latest - if: needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure' + if: always() && (needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure') permissions: actions: write contents: read diff --git a/actions/pipeline-manager/__snapshots__/chn-openstates-test/mt-legislation/.github/workflows/extract-text.yml b/actions/pipeline-manager/__snapshots__/chn-openstates-test/mt-legislation/.github/workflows/extract-text.yml index 385f37e..53b9e8e 100644 --- a/actions/pipeline-manager/__snapshots__/chn-openstates-test/mt-legislation/.github/workflows/extract-text.yml +++ b/actions/pipeline-manager/__snapshots__/chn-openstates-test/mt-legislation/.github/workflows/extract-text.yml @@ -66,12 +66,20 @@ jobs: echo "⚠️ Summary file not found" fi - # Auto-restart job if extraction was cancelled (timeout) or failed + # Auto-restart job if extraction was cancelled (timeout) or failed. + # + # The `always() &&` is load-bearing, not decoration: GitHub Actions + # implicitly ANDs a job's `if:` with success() unless the condition + # explicitly calls a status-check function (always/success/failure/ + # cancelled). Without it, this job silently skipped every single time -- + # confirmed across every real failure/cancellation this repo has hit so + # far (it never once ran) -- because the needed job having failed made + # the implicit success() false regardless of the explicit check below. check-and-restart: name: Check Status & Restart if Needed needs: extract-text runs-on: ubuntu-latest - if: needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure' + if: always() && (needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure') permissions: actions: write contents: read diff --git a/actions/pipeline-manager/__snapshots__/chn-openstates-test/pr-legislation/.github/workflows/extract-text.yml b/actions/pipeline-manager/__snapshots__/chn-openstates-test/pr-legislation/.github/workflows/extract-text.yml index a3b8e78..2910eab 100644 --- a/actions/pipeline-manager/__snapshots__/chn-openstates-test/pr-legislation/.github/workflows/extract-text.yml +++ b/actions/pipeline-manager/__snapshots__/chn-openstates-test/pr-legislation/.github/workflows/extract-text.yml @@ -66,12 +66,20 @@ jobs: echo "⚠️ Summary file not found" fi - # Auto-restart job if extraction was cancelled (timeout) or failed + # Auto-restart job if extraction was cancelled (timeout) or failed. + # + # The `always() &&` is load-bearing, not decoration: GitHub Actions + # implicitly ANDs a job's `if:` with success() unless the condition + # explicitly calls a status-check function (always/success/failure/ + # cancelled). Without it, this job silently skipped every single time -- + # confirmed across every real failure/cancellation this repo has hit so + # far (it never once ran) -- because the needed job having failed made + # the implicit success() false regardless of the explicit check below. check-and-restart: name: Check Status & Restart if Needed needs: extract-text runs-on: ubuntu-latest - if: needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure' + if: always() && (needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure') permissions: actions: write contents: read diff --git a/actions/pipeline-manager/__snapshots__/chn-openstates-test/wy-legislation/.github/workflows/extract-text.yml b/actions/pipeline-manager/__snapshots__/chn-openstates-test/wy-legislation/.github/workflows/extract-text.yml index 04833bb..1f5994b 100644 --- a/actions/pipeline-manager/__snapshots__/chn-openstates-test/wy-legislation/.github/workflows/extract-text.yml +++ b/actions/pipeline-manager/__snapshots__/chn-openstates-test/wy-legislation/.github/workflows/extract-text.yml @@ -66,12 +66,20 @@ jobs: echo "⚠️ Summary file not found" fi - # Auto-restart job if extraction was cancelled (timeout) or failed + # Auto-restart job if extraction was cancelled (timeout) or failed. + # + # The `always() &&` is load-bearing, not decoration: GitHub Actions + # implicitly ANDs a job's `if:` with success() unless the condition + # explicitly calls a status-check function (always/success/failure/ + # cancelled). Without it, this job silently skipped every single time -- + # confirmed across every real failure/cancellation this repo has hit so + # far (it never once ran) -- because the needed job having failed made + # the implicit success() false regardless of the explicit check below. check-and-restart: name: Check Status & Restart if Needed needs: extract-text runs-on: ubuntu-latest - if: needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure' + if: always() && (needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure') permissions: actions: write contents: read diff --git a/actions/pipeline-manager/templates/openstates-to-ocd-files/.github/workflows/extract-text.yml b/actions/pipeline-manager/templates/openstates-to-ocd-files/.github/workflows/extract-text.yml index b63ab5a..8bc29ae 100644 --- a/actions/pipeline-manager/templates/openstates-to-ocd-files/.github/workflows/extract-text.yml +++ b/actions/pipeline-manager/templates/openstates-to-ocd-files/.github/workflows/extract-text.yml @@ -66,12 +66,20 @@ jobs: echo "⚠️ Summary file not found" fi - # Auto-restart job if extraction was cancelled (timeout) or failed + # Auto-restart job if extraction was cancelled (timeout) or failed. + # + # The `always() &&` is load-bearing, not decoration: GitHub Actions + # implicitly ANDs a job's `if:` with success() unless the condition + # explicitly calls a status-check function (always/success/failure/ + # cancelled). Without it, this job silently skipped every single time -- + # confirmed across every real failure/cancellation this repo has hit so + # far (it never once ran) -- because the needed job having failed made + # the implicit success() false regardless of the explicit check below. check-and-restart: name: Check Status & Restart if Needed needs: extract-text runs-on: ubuntu-latest - if: needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure' + if: always() && (needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure') permissions: actions: write contents: read From 58f7e428fdf38e4a332b5af302f22858f3622d7d Mon Sep 17 00:00:00 2001 From: Tamara Dowis Date: Mon, 20 Jul 2026 23:44:01 -0500 Subject: [PATCH 2/2] fix: same implicit-success() bug in the example caller-repo workflow Same root cause and fix as the previous commit, found while checking whether this pattern appeared elsewhere in the repo. Co-Authored-By: Claude Sonnet 5 --- .../for-caller-repos/example-caller-text-extraction.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/actions/format/docs/for-caller-repos/example-caller-text-extraction.yml b/actions/format/docs/for-caller-repos/example-caller-text-extraction.yml index 0e617a8..84a3c5c 100644 --- a/actions/format/docs/for-caller-repos/example-caller-text-extraction.yml +++ b/actions/format/docs/for-caller-repos/example-caller-text-extraction.yml @@ -41,12 +41,17 @@ jobs: echo "⚠️ Summary file not found" fi - # Auto-restart job if extraction was cancelled (timeout) or failed + # Auto-restart job if extraction was cancelled (timeout) or failed. + # The `always() &&` is load-bearing: GitHub Actions implicitly ANDs a + # job's if: with success() unless the condition calls a status-check + # function (always/success/failure/cancelled) -- without it, this job + # silently skips every time, since the needed job having failed makes + # the implicit success() false regardless of the explicit check below. check-and-restart: name: Check Status & Restart if Needed needs: extract-text runs-on: ubuntu-latest - if: needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure' + if: always() && (needs.extract-text.result == 'cancelled' || needs.extract-text.result == 'failure') permissions: actions: write contents: read