feat: opt-in coverage comment in the CI pipeline#140
Conversation
The reusable build workflow published JUnit results and uploaded coverage HTML as an artifact, but never surfaced coverage on the pull request. Add a `jvm-coverage` composite action that summarises JaCoCo-format XML reports (emitted by both JaCoCo and Kotlinx Kover) into one Markdown table and posts/updates a single idempotent PR comment via github-script. Wire it into ci.yml behind a new `coverage` input (default false, so existing consumers are unaffected) and a `coverage-xml` glob input. The comment is gated to same-repo pull requests, since fork PRs receive a read-only token. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds two ChangesJVM Coverage PR Comment Feature
Sequence Diagram(s)sequenceDiagram
participant Caller as Calling Workflow
participant CI as CI Runner
participant FS as File System (Artifacts)
participant GH as GitHub API
Caller->>CI: trigger with coverage=true, coverage-xml glob
CI->>FS: glob matching XML paths
FS-->>CI: JaCoCo/Kover XML files
CI->>CI: parse <counter> elements per module
CI->>FS: write jvm-coverage-summary.md
CI->>GH: GET /repos/.../issues/.../comments
GH-->>CI: list of PR comments
alt existing bot comment with marker found
CI->>GH: PATCH comment (update)
else no existing comment
CI->>GH: POST new comment
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Default `coverage` to true and make the action a no-op when no JaCoCo-format XML matches the glob: it logs a notice and skips the comment instead of posting an empty one. This makes coverage zero-config for repos that emit JaCoCo/Kover XML and invisible for those that don't, so it is safe on by default. Repos can still set `coverage: false` to opt out entirely. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Update (commit de433e5): the |
A reusable workflow can only reference actions by a published ref, and a new composite action does not exist on the v2 tag until release, so referencing jvm-coverage@v2 from ci.yml broke the workflow (and its own self-test) with "Can't find action.yml". Inline the coverage summary (python) and comment (github-script) steps directly into the build job; inline run steps and the published github-script action resolve for every consumer with no new tag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Test Results (spring-skeleton)1 tests 1 ✅ 2s ⏱️ Results for commit 8c31767. |
Test Results (spring-skeleton)2 tests 2 ✅ 2s ⏱️ Results for commit 8c31767. ♻️ This comment has been updated with latest results. |
Test Results (KTOR-SKELETON)2 tests 2 ✅ 3s ⏱️ Results for commit 8c31767. ♻️ This comment has been updated with latest results. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/ci.yml:
- Around line 133-136: The ET.parse(path) call in the coverage XML parsing
section lacks error handling and will raise an exception if the XML is
malformed, causing the entire workflow to fail. Wrap the ET.parse(path)
statement and subsequent operations (including the attrib.get, counters call,
and line retrieval) in a try-except block that catches XML parsing errors. When
an exception occurs, log a warning message that includes the file path and the
actual error details, then skip processing that file by continuing to the next
iteration. This allows the workflow to gracefully degrade by warning about bad
files while continuing to process valid coverage files.
- Line 171: The actions/github-script action is using a mutable version tag
(`@v9`) which weakens supply-chain security. Replace the version tag `@v9` with the
full commit SHA 3a2844b7e9c422d3c10d287c895573f7108da1b3 in the uses field, and
add a comment on the same line (# v9.0.0) for readability to indicate which
version this commit corresponds to.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| root = ET.parse(path).getroot() | ||
| name = root.attrib.get("name") or path | ||
| module = counters(root) | ||
| line = module.get("LINE", (0, 0)) |
There was a problem hiding this comment.
Handle malformed coverage XML without failing the whole workflow.
A single malformed/misdetected XML causes ET.parse(path) to raise and fail the job. Since this feature is enabled by default and intended to be no-op-safe, this should degrade gracefully (warn + skip bad file), not hard-fail CI.
Suggested patch
rows = []
totals = {}
+ parsed_any = False
for path in files:
- root = ET.parse(path).getroot()
+ try:
+ root = ET.parse(path).getroot()
+ except ET.ParseError:
+ print(f"::warning file={path}::Invalid coverage XML; skipping.")
+ continue
name = root.attrib.get("name") or path
module = counters(root)
line = module.get("LINE", (0, 0))
branch = module.get("BRANCH", (0, 0))
rows.append(f"| {name} | {percentage(*line)} | {percentage(*branch)} |")
+ parsed_any = True
for counter_type, (covered, missed) in module.items():
acc_covered, acc_missed = totals.get(counter_type, (0, 0))
totals[counter_type] = (acc_covered + covered, acc_missed + missed)
+
+ if not parsed_any:
+ print("::notice::No valid coverage XML parsed; skipping the coverage comment.")
+ raise SystemExit(0)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci.yml around lines 133 - 136, The ET.parse(path) call in
the coverage XML parsing section lacks error handling and will raise an
exception if the XML is malformed, causing the entire workflow to fail. Wrap the
ET.parse(path) statement and subsequent operations (including the attrib.get,
counters call, and line retrieval) in a try-except block that catches XML
parsing errors. When an exception occurs, log a warning message that includes
the file path and the actual error details, then skip processing that file by
continuing to the next iteration. This allows the workflow to gracefully degrade
by warning about bad files while continuing to process valid coverage files.
|
|
||
| - name: Comment coverage on the pull request | ||
| if: ${{ steps.coverage.outputs.has_reports == 'true' }} | ||
| uses: actions/github-script@v9 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify unpinned action refs in workflow files
rg -nP '^\s*uses:\s*[^@]+@[A-Za-z0-9._-]+$' .github/workflowsRepository: yonatankarp/github-actions
Length of output: 2086
🏁 Script executed:
sed -n '165,180p' .github/workflows/ci.ymlRepository: yonatankarp/github-actions
Length of output: 674
🏁 Script executed:
rg 'actions/github-script' .github/workflows/ci.yml -nRepository: yonatankarp/github-actions
Length of output: 113
🌐 Web query:
GitHub Actions pinning commit SHA security best practice actions/github-script v9
💡 Result:
Pinning GitHub Actions to a full-length commit SHA is a security best practice that protects against supply chain attacks, such as malicious code being injected into an existing tag or branch [1][2][3]. Because tags and branches are mutable references that can be moved or force-pushed, a compromised action repository could silently inject malicious changes into your workflows [4][3]. A full commit SHA is immutable, ensuring that your workflow runs exactly the code you intend [5][2][6]. For actions/github-script v9, which was released in April 2026 [7][8], the recommended security approach is to pin to its specific full commit SHA [5][2]. The current SHA for version 9.0.0 is 3a2844b7e9c422d3c10d287c895573f7108da1b3 [7][8]. When pinning to a SHA, it is a standard practice to include the version tag as a comment for readability and to facilitate automated updates by tools like Dependabot or Renovate [2][9][3]: - uses: actions/github-script@3a2844b # v9.0.0 Key considerations for this practice include: 1. Enforcement: Many organizations now enforce SHA pinning via policy to prevent the use of mutable tags [10][5][2]. If your organization enables this, workflows using tags will fail [5][2]. 2. Maintenance: Using Dependabot or Renovate with appropriate presets (e.g., helpers:pinGitHubActionDigests) allows you to automate the process of updating these pinned SHAs, balancing security with ease of maintenance [2][4][11]. 3. Dependency Trees: Security policies that enforce SHA pinning may check the entire dependency tree of your actions, including internal sub-actions referenced by the action you are using [5]. 4. V9 Specifics: Note that actions/github-script v9 introduced breaking changes, including an upgrade to @actions/github v9 (which is ESM-only) and the requirement to use an injected getOctokit function instead of requiring @actions/github [12][7][13]. When updating to v9, ensure your scripts are compatible with these changes [12][13].
Citations:
- 1: https://docs.github.com/en/actions/reference/security/secure-use
- 2: https://architecture.lullabot.com/adr/20260505-pin-github-actions-to-hash/
- 3: https://safeguard.sh/resources/blog/how-to-pin-github-actions-to-shas-correctly
- 4: https://github.com/kubernetes/community/blob/main/github-management/github-actions-policy.md
- 5: https://www.romainlespinasse.dev/posts/github-actions-commit-sha-pinning/
- 6: https://docs.github.com/en/actions/how-tos/create-and-publish-actions/manage-custom-actions
- 7: actions/github-script@3a2844b
- 8: feat: update actions/github-script to v9.0.0 with builtin getOctokit github/gh-aw#25553
- 9: https://safeguard.sh/resources/blog/github-actions-pinning-sha-vs-tag
- 10: https://github.blog/changelog/2025-08-15-github-actions-policy-now-supports-blocking-and-sha-pinning-actions/
- 11: https://emmer.dev/blog/pin-your-github-actions-to-protect-against-mutability/
- 12: https://github.com/actions/github-script/
- 13: https://github.com/marketplace/actions/github-script
🌐 Web query:
actions/github-script v9 commit SHA release
💡 Result:
The actions/github-script v9.0.0 release is associated with the commit SHA 3a2844b7e9c422d3c10d287c895573f7108da1b3 [1][2]. This release, published on April 9, 2026, upgraded the action to use @actions/github v9 [3][1]. Notable changes in v9 include: - The addition of a getOctokit factory function available directly in the script context [1][4]. - An upgrade to an ESM-only version of @actions/github, which means require('@actions/github') no longer works in scripts [1][5]. - The requirement to use the injected getOctokit function instead of importing it from @actions/github [1][6]. - The automatic addition of an orchestration ID to the user-agent string for request tracing [4][5].
Citations:
- 1: actions/github-script@3a2844b
- 2: feat!: add getOctokit to script context, upgrade @actions/github v9, @octokit/core v7, and related packages actions/github-script#700
- 3: https://github.com/actions/github-script/releases/tag/v9.0.0
- 4: https://github.com/actions/github-script/releases
- 5: https://github.com/marketplace/actions/github-script
- 6: actions/github-script@3953caf
Pin actions/github-script to a full commit SHA.
Using @v9 is mutable and weakens supply-chain guarantees. Pin to the exact commit SHA 3a2844b7e9c422d3c10d287c895573f7108da1b3 (with # v9.0.0 comment for readability) to protect against tag manipulation and supply-chain attacks.
🧰 Tools
🪛 zizmor (1.25.2)
[error] 171-171: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)
(unpinned-uses)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/ci.yml at line 171, The actions/github-script action is
using a mutable version tag (`@v9`) which weakens supply-chain security. Replace
the version tag `@v9` with the full commit SHA
3a2844b7e9c422d3c10d287c895573f7108da1b3 in the uses field, and add a comment on
the same line (# v9.0.0) for readability to indicate which version this commit
corresponds to.
Source: Linters/SAST tools
Why
The reusable
ci.ymlbuild workflow publishes JUnit results and uploads coverage HTML as an artifact, but never surfaces coverage on the pull request itself. Repos that hand-roll their pipeline (e.g. agent-desk) post a coverage comment themselves; repos that use this wrapper get nothing.What
jvm-coverage: parses JaCoCo-format coverage XML, builds a per-module + total Markdown table, and posts/updates one idempotent PR comment (matched by a hidden marker) viaactions/github-script. JaCoCo and Kotlinx Kover both emit JaCoCo-format XML, so the same action serves either tool.ci.ymlbehind a newcoverageinput (defaultfalse— existing consumers are unaffected) plus acoverage-xmlglob input. The step is gated to same-repo pull requests, since fork PRs receive a read-only token.Enabling it (consumer side)
The consumer's build must produce the XML (e.g. JaCoCo
xml.required = true+ a report task wired into the build).Verified
The summary script was run against a real multi-module JaCoCo set (beat-the-machine: domain/application/adapters) and produced the expected per-module + total table. YAML validated.
Release
After merge, move the
v2tag (sh ./bin/replace-tags.sh v2) so consumers pick it up. beat-the-machine will be the first consumer (coverage: true).Summary by CodeRabbit