From 0ea24b059002ae58837adeaeebe6eacccb0fc0f2 Mon Sep 17 00:00:00 2001 From: Anshoo Date: Wed, 9 Sep 2026 20:49:04 -0500 Subject: [PATCH 1/5] BACON-490: Pin codecov-action v7 and expose coverage inputs. Existing callers keep working: new inputs are optional and the upload stays non-blocking unless coverage-fail-on-error is set. Co-authored-by: Cursor --- .github/workflows/ruby-test-matrix.yml | 44 +++++++++++++++++++++++--- README.md | 33 +++++++++++++++++++ 2 files changed, 72 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ruby-test-matrix.yml b/.github/workflows/ruby-test-matrix.yml index 033f3e7..62bc5b9 100644 --- a/.github/workflows/ruby-test-matrix.yml +++ b/.github/workflows/ruby-test-matrix.yml @@ -15,10 +15,38 @@ on: required: false default: "bundle exec rspec" type: string + coverage-files: + description: >- + Comma-separated coverage report paths to upload. + Set to an empty string to disable the upload step entirely. + required: false + default: "coverage/lcov.info" + type: string + coverage-flags: + description: >- + Comma-separated Codecov flags used to group coverage metrics. + Each flag must match ^[\w\.\-]{1,45}$ (no slashes). + required: false + default: "" + type: string + coverage-fail-on-error: + description: >- + Fail the job when the Codecov upload errors. + False by default, matching the informational posture used everywhere else. + required: false + default: false + type: boolean + coverage-name: + description: Label shown for this upload in the Codecov UI. Defaults to the matrix cell. + required: false + default: "" + type: string secrets: CODECOV_TOKEN: required: false - description: Optional Codecov upload token for coverage reports + description: >- + Org-level Codecov upload token. One value works for every repository. + Omit on public repositories to fall back to tokenless upload. jobs: gemfile-paths: @@ -61,12 +89,18 @@ jobs: timeout_minutes: 10 command: ${{ inputs.test-command }} - name: Upload coverage reports to Codecov - continue-on-error: true - if: ${{ hashFiles('coverage/lcov.info') != '' }} - uses: codecov/codecov-action@v5 + if: ${{ inputs.coverage-files != '' }} + continue-on-error: ${{ !inputs.coverage-fail-on-error }} + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: token: ${{ secrets.CODECOV_TOKEN }} - files: coverage/lcov.info + slug: ${{ github.repository }} + files: ${{ inputs.coverage-files }} + flags: ${{ inputs.coverage-flags }} + name: ${{ inputs.coverage-name || format('ruby-{0}-{1}', matrix.ruby, matrix.gemfile) }} + disable_search: true + handle_no_reports_found: true + fail_ci_if_error: ${{ inputs.coverage-fail-on-error }} build_success: if: always() diff --git a/README.md b/README.md index a019709..185a321 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Other workflows can run this workflow similar to using a Github Action jobs: tests: uses: Invoca/ruby-test-matrix-workflow/.github/workflows/ruby-test-matrix.yml@main + secrets: inherit ``` This default usage will run the rspec test suite using the following matrix strategy @@ -21,6 +22,8 @@ matrix: gemfile: // All files from the calling repo that ends with `*gemfile` ``` +`secrets: inherit` passes the org-level `CODECOV_TOKEN` into the reusable workflow. One token works for every repository; there is nothing to provision per repo. Public repositories can omit the token and fall back to tokenless upload. Fork pull requests always take that fallback, because GitHub does not pass secrets to workflows triggered by `pull_request` from a fork. + ### Available Parameters Parameters can be passed to the workflow using the usual Github Action syntax @@ -48,3 +51,33 @@ with: with: test-command: "bundle exec rake test" ``` + +## Codecov + +After tests, each matrix cell uploads coverage with `codecov/codecov-action` pinned to v7.0.0. The house convention is a single LCOV file at `coverage/lcov.info`. Copy [Invoca/escalate/spec/simplecov_helper.rb](https://github.com/Invoca/escalate/blob/master/spec/simplecov_helper.rb) (and `gem "simplecov", "~> 0.22"` plus `gem "simplecov-lcov", "~> 0.8"`) so the tests actually write that file. Without it the upload step still runs, reports that nothing was found, and the job stays green. + +These inputs are the GitHub Actions spelling of the same three concepts as the [codecov-buildkite-plugin](https://github.com/Invoca/codecov-buildkite-plugin): files, flags, and fail on error. + +* `coverage-files`: (string, optional) Comma-separated coverage report paths to upload. Set to an empty string to disable the upload step entirely. Defaults to `"coverage/lcov.info"`. + ```yaml + with: + coverage-files: "coverage/lcov.info" + ``` + +* `coverage-flags`: (string, optional) Comma-separated Codecov flags used to group coverage metrics. Each flag must match `^[\w\.\-]{1,45}$` (no slashes). Defaults to `""`. + ```yaml + with: + coverage-flags: "ruby" + ``` + +* `coverage-fail-on-error`: (boolean, optional) Fail the job when the Codecov upload errors. False by default, matching the informational posture used everywhere else. Pass `true` to make a failed upload fail the job. + ```yaml + with: + coverage-fail-on-error: true + ``` + +* `coverage-name`: (string, optional) Label shown for this upload in the Codecov UI. Defaults to the matrix cell (`ruby--`). + ```yaml + with: + coverage-name: "unit" + ``` From 5fedc2b5856794a290707c19a725478a8b8c3b53 Mon Sep 17 00:00:00 2001 From: Anshoo Date: Fri, 11 Sep 2026 15:17:54 -0500 Subject: [PATCH 2/5] Quote continue-on-error so YAML does not eat the ! operator. Unquoted ${{ !inputs... }} is parsed as a YAML tag, so continue-on-error stayed true and coverage-fail-on-error could not fail the job. Co-authored-by: Cursor --- .github/workflows/ruby-test-matrix.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ruby-test-matrix.yml b/.github/workflows/ruby-test-matrix.yml index 62bc5b9..df472aa 100644 --- a/.github/workflows/ruby-test-matrix.yml +++ b/.github/workflows/ruby-test-matrix.yml @@ -90,7 +90,8 @@ jobs: command: ${{ inputs.test-command }} - name: Upload coverage reports to Codecov if: ${{ inputs.coverage-files != '' }} - continue-on-error: ${{ !inputs.coverage-fail-on-error }} + # Quote the expression so YAML does not treat `!` as a tag. + continue-on-error: "${{ !inputs.coverage-fail-on-error }}" uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: token: ${{ secrets.CODECOV_TOKEN }} From 740bf328c72247c569e3efc761ba8168f4c07516 Mon Sep 17 00:00:00 2001 From: Anshoo Date: Fri, 11 Sep 2026 15:19:28 -0500 Subject: [PATCH 3/5] Evaluate continue-on-error as a boolean, not a quoted string. Quoted "false" is still truthy to GitHub Actions, so the hard-fail input could not fail the job even after quoting the YAML ! tag. Co-authored-by: Cursor --- .github/workflows/ruby-test-matrix.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ruby-test-matrix.yml b/.github/workflows/ruby-test-matrix.yml index df472aa..79b86d5 100644 --- a/.github/workflows/ruby-test-matrix.yml +++ b/.github/workflows/ruby-test-matrix.yml @@ -90,8 +90,9 @@ jobs: command: ${{ inputs.test-command }} - name: Upload coverage reports to Codecov if: ${{ inputs.coverage-files != '' }} - # Quote the expression so YAML does not treat `!` as a tag. - continue-on-error: "${{ !inputs.coverage-fail-on-error }}" + # Avoid `${{ !... }}` (YAML tag) and quoted "false" (GHA treats a + # non-empty string as true for continue-on-error). Compare as boolean. + continue-on-error: ${{ inputs.coverage-fail-on-error == false }} uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: token: ${{ secrets.CODECOV_TOKEN }} From 166feba7d1b33539eee98c882ccd0a3c637c3186 Mon Sep 17 00:00:00 2001 From: Anshoo Date: Fri, 11 Sep 2026 15:21:15 -0500 Subject: [PATCH 4/5] Debug coverage-fail-on-error and coerce it with fromJSON. Temporary: confirm how GitHub Actions types the boolean input so continue-on-error can actually fail the job. Co-authored-by: Cursor --- .github/workflows/ruby-test-matrix.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ruby-test-matrix.yml b/.github/workflows/ruby-test-matrix.yml index 79b86d5..a515346 100644 --- a/.github/workflows/ruby-test-matrix.yml +++ b/.github/workflows/ruby-test-matrix.yml @@ -88,11 +88,15 @@ jobs: max_attempts: 2 timeout_minutes: 10 command: ${{ inputs.test-command }} + - name: Debug coverage-fail-on-error + run: | + echo "raw=${{ inputs.coverage-fail-on-error }}" + echo "eq_false=${{ inputs.coverage-fail-on-error == false }}" + echo "ne_true=${{ inputs.coverage-fail-on-error != true }}" + echo "fromjson=${{ fromJSON(inputs.coverage-fail-on-error) }}" - name: Upload coverage reports to Codecov if: ${{ inputs.coverage-files != '' }} - # Avoid `${{ !... }}` (YAML tag) and quoted "false" (GHA treats a - # non-empty string as true for continue-on-error). Compare as boolean. - continue-on-error: ${{ inputs.coverage-fail-on-error == false }} + continue-on-error: ${{ fromJSON(inputs.coverage-fail-on-error) == false }} uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: token: ${{ secrets.CODECOV_TOKEN }} From bb3993da244b1d49c80f793467d366534ebf2486 Mon Sep 17 00:00:00 2001 From: Anshoo Date: Fri, 11 Sep 2026 15:24:08 -0500 Subject: [PATCH 5/5] Split Codecov upload into blocking and non-blocking steps. continue-on-error cannot take a boolean expression reliably, so each posture is a hardcoded YAML boolean selected by if. Co-authored-by: Cursor --- .github/workflows/ruby-test-matrix.yml | 27 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ruby-test-matrix.yml b/.github/workflows/ruby-test-matrix.yml index a515346..6ceafe7 100644 --- a/.github/workflows/ruby-test-matrix.yml +++ b/.github/workflows/ruby-test-matrix.yml @@ -88,15 +88,12 @@ jobs: max_attempts: 2 timeout_minutes: 10 command: ${{ inputs.test-command }} - - name: Debug coverage-fail-on-error - run: | - echo "raw=${{ inputs.coverage-fail-on-error }}" - echo "eq_false=${{ inputs.coverage-fail-on-error == false }}" - echo "ne_true=${{ inputs.coverage-fail-on-error != true }}" - echo "fromjson=${{ fromJSON(inputs.coverage-fail-on-error) }}" + # Two steps so continue-on-error is a real YAML boolean. An expression + # here is unreliable: GitHub treats a quoted "false" as true, and + # fromJSON('') blows up when the boolean input is the default. - name: Upload coverage reports to Codecov - if: ${{ inputs.coverage-files != '' }} - continue-on-error: ${{ fromJSON(inputs.coverage-fail-on-error) == false }} + if: ${{ inputs.coverage-files != '' && !inputs.coverage-fail-on-error }} + continue-on-error: true uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: token: ${{ secrets.CODECOV_TOKEN }} @@ -106,7 +103,19 @@ jobs: name: ${{ inputs.coverage-name || format('ruby-{0}-{1}', matrix.ruby, matrix.gemfile) }} disable_search: true handle_no_reports_found: true - fail_ci_if_error: ${{ inputs.coverage-fail-on-error }} + fail_ci_if_error: false + - name: Upload coverage reports to Codecov + if: ${{ inputs.coverage-files != '' && inputs.coverage-fail-on-error }} + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + slug: ${{ github.repository }} + files: ${{ inputs.coverage-files }} + flags: ${{ inputs.coverage-flags }} + name: ${{ inputs.coverage-name || format('ruby-{0}-{1}', matrix.ruby, matrix.gemfile) }} + disable_search: true + handle_no_reports_found: true + fail_ci_if_error: true build_success: if: always()