Skip to content

feat: 汎用カバレッジレポート PR コメント投稿の reusable workflow 追加 - #496

Merged
keito4 merged 4 commits into
mainfrom
feat/495-coverage-report-workflow
Feb 21, 2026
Merged

feat: 汎用カバレッジレポート PR コメント投稿の reusable workflow 追加#496
keito4 merged 4 commits into
mainfrom
feat/495-coverage-report-workflow

Conversation

@keito4

@keito4 keito4 commented Feb 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • 汎用カバレッジレポート reusable workflow (coverage-report.yml) を新規追加
  • 4形式のカバレッジレポートに対応: jacoco, jest, cobertura, lcov
  • unified-ci.yml テンプレートのハードコードされたカバレッジジョブを reusable workflow 呼び出しに置換
  • テンプレート README にサポート形式・使用例・トラブルシューティングを追記
  • BATS 統合テスト 15件を追加

Supported Formats

format Tools Action
jacoco JaCoCo (Android/JVM) madrapps/jacoco-report@v1.7.2
jest Jest, Vitest, c8 (JS/TS) actions/github-script@v8
cobertura Cobertura (.NET/Python/Go) irongut/CodeCoverageSummary@v1.3.0
lcov Istanbul, nyc, c8 (LCOV) romeovs/lcov-reporter-action@v0.4.0

Test plan

  • Jest テスト 101件通過
  • BATS 統合テスト 15件通過(YAML構造、inputs、permissions、全4形式の条件分岐)
  • Prettier format check 通過
  • ESLint 通過
  • 実際の PR での動作確認(各形式で artifact アップロード → reusable workflow 呼び出し)

Closes #495

🤖 Generated with Claude Code

github-actions Bot and others added 3 commits February 21, 2026 21:46
jacoco (Android/JVM) と jest (JS/TS) 形式をサポートする
workflow_call ベースの reusable workflow を新規作成。
BATS テスト 11 件を追加。

Closes #495

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
coverage-report.yml の説明、使用例(jest/jacoco)、
トラブルシューティングを追加。Coverage Format セクションを
reusable workflow への移行案内に更新。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
coverage-report ジョブの 80 行のハードコード(Node setup + npm ci +
npm test + github-script)を coverage-report.yml reusable workflow の
10 行の呼び出しに簡素化。quality-checks に artifact upload を追加。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Feb 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

A reusable GitHub Actions workflow is introduced to post coverage reports to PRs, supporting JaCoCo and Jest formats. The unified-ci template is refactored to invoke this external workflow instead of handling coverage reporting inline, with comprehensive documentation and integration tests added.

Changes

Cohort / File(s) Summary
Reusable Workflow
.github/workflows/coverage-report.yml
New reusable workflow accepting format, report-path, and optional thresholds as inputs. Provides two conditional jobs: report-jacoco uses madrapps/jacoco-report action; report-jest parses coverage-summary.json and posts/updates Markdown comment via GitHub Script with per-package table formatting.
Template Updates
.github/workflows/templates/unified-ci.yml
Replaces inline coverage reporting steps with workflow_call to coverage-report.yml. Adds artifact upload for coverage-summary.json from Quality Checks job, gated to PRs. Delegates all coverage logic to reusable workflow.
Documentation
.github/workflows/templates/README.md
Adds documentation for coverage-report reusable workflow, including supported formats (jacoco, jest), usage examples, input descriptions, and troubleshooting guidance. Updates references from inline coverage jobs to reusable workflow pattern.
Integration Tests
test/integration/coverage-report-workflow.bats
New test suite validating workflow YAML structure, workflow_call trigger, required/optional inputs, permissions, job references to madrapps/jacoco-report and actions/github-script, pinned action versions, artifact download logic, and environment variable usage.

Sequence Diagram(s)

sequenceDiagram
    participant CI as unified-ci.yml
    participant WF as coverage-report.yml
    participant Artifact as Artifact Storage
    participant Action as JaCoCo/GitHub Script
    participant PR as PR Comments

    CI->>CI: Quality Checks Job<br/>(generate coverage report)
    CI->>Artifact: Upload coverage artifact
    CI->>WF: Invoke reusable workflow<br/>(format, report-path)
    
    alt format == 'jacoco'
        WF->>Artifact: Download coverage artifact
        WF->>Action: Call madrapps/jacoco-report
        Action->>PR: Post JaCoCo report
    else format == 'jest'
        WF->>Artifact: Download coverage artifact
        WF->>Action: Parse coverage-summary.json<br/>via GitHub Script
        Action->>PR: Find existing comment by title
        alt Comment exists
            Action->>PR: Update existing comment
        else Comment not found
            Action->>PR: Create new comment
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested labels

released

Poem

🐰 A reusable hopper bounds forth,
Coverage reports north, south, east, and west,
JaCoCo and Jest both pass the test,
No more duplicate comments—just one blessed!
The workflows stay dry, the templatesâ mortised true,

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is in Japanese and directly describes the main feature: introducing a reusable workflow for posting generic coverage reports to PR comments. This aligns precisely with the PR's primary objective.
Linked Issues check ✅ Passed All primary objectives from issue #495 are met: reusable coverage-report.yml workflow created with jacoco and jest format support, artifact-based coverage reporting, threshold configuration, and unified-ci.yml template refactored to use the reusable workflow.
Out of Scope Changes check ✅ Passed All changes are scoped to the coverage reporting objective: adding the reusable workflow, integrating it into unified-ci.yml, updating documentation, and adding comprehensive integration tests. No unrelated modifications detected.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/495-coverage-report-workflow

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@keito4

keito4 commented Feb 21, 2026

Copy link
Copy Markdown
Owner Author

🔍 AI Code Review (Local Hook)

Models: Codex (default) / Gemini (default)

🤖 Codex Review

指摘事項

  • 重大: 生成した成果物のパスと report-path が不整合なため、カバレッジファイルが見つからずPRコメントが投稿されません。actions/upload-artifactcoverage/coverage-summary.json をアップロードすると、ダウンロード後の実体は coverage-download/coverage/coverage-summary.json になりますが、呼び出し側は report-path: coverage-summary.json を指定しており、coverage-download/coverage-summary.json を探して失敗します。.github/workflows/templates/unified-ci.yml:104.github/workflows/templates/unified-ci.yml:118.github/workflows/coverage-report.yml:104
  • 重大: ドキュメント例も同じ不整合を含み、そのままコピペすると動作しません。Jest例とJaCoCo例の report-path が、アップロードしたパスのディレクトリを含んでいません。.github/workflows/templates/README.md:88.github/workflows/templates/README.md:96.github/workflows/templates/README.md:120.github/workflows/templates/README.md:128
  • 中: min-coverage-overall / min-coverage-changed-files は入力として定義されていますが、Jest系のジョブでは一切使用されず、閾値チェックが行われません。READMEの「Configurable coverage thresholds」と齟齬が出ます。.github/workflows/coverage-report.yml:32.github/workflows/coverage-report.yml:92.github/workflows/templates/README.md:65

総合判定
patch is incorrect。理由: 現状の report-path 指定が成果物の実パスと一致せず、Jest/JaCoCo どちらでもカバレッジコメントが出ないため。信頼度 0.64


⚠️ 修正が必要です

上記のレビューで問題が指摘されています。修正してからマージしてください。


🤖 Generated by post_pr_ai_review.py hook

@claude

claude Bot commented Feb 21, 2026

Copy link
Copy Markdown
Contributor

PR レビュー

全体として、80行のハードコードを reusable workflow に抽出するリファクタリングの方向性は正しく、DRY原則に沿っています。BATS テストの追加も適切です。いくつか改善点を挙げます。


🐛 バグ / 設計上の問題

1. min-coverage-overall / min-coverage-changed-files が jest では無視される

coverage-report.yml の inputs に閾値パラメータが定義されているにもかかわらず、report-jest ジョブではこれらを使用していません。jacoco のみ madrapps/jacoco-report アクションが閾値チェックを行います。

# report-jest 内に閾値チェックがない
# min-coverage-overall: ${{ inputs.min-coverage-overall }} が利用されていない

呼び出し元が min-coverage-overall: 70 を設定しても jest 形式では一切チェックされず、サイレントに通過します。対応案:

  • jest スクリプト内で total.lines.pct を読んで閾値と比較し、core.setFailed() で失敗させる
  • または、inputs の説明に「jest 形式では無効」と明記する

2. 不正な format 値がサイレント no-op になる

format: python など未対応の値が渡されると、report-jacocoreport-jestif: 条件が false になり、ジョブが全てスキップされてエラーなし。呼び出し元が気付けない。

# どちらも実行されずに緑になる
if: inputs.format == 'jacoco'
if: inputs.format == 'jest'

対応案:バリデーションステップを追加する、または GitHub Actions の choice 型 input を使う(ただし workflow_call では choice 型は未サポートのため、スクリプトでの検証が現実的)。


⚠️ コード品質

3. report-jest ジョブの Checkout code ステップが不要

report-jest:
  steps:
    - name: Checkout code        # ← artifact DL とコメント投稿だけなので不要
      uses: actions/checkout@v6.0.2

Jest レポートジョブはアーティファクトのダウンロードと PR コメント投稿だけを行うため、コードのチェックアウトは不要です。ジョブ実行時間の削減とセキュリティ面(不必要な権限行使を避ける)で削除を推奨します。

なお report-jacocofetch-depth: 0madrapps/jacoco-report が差分ファイル検出に使用するため必要です。

4. JSON パース時のエラーハンドリングが不足

const coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf8'));
// ^ JSON が壊れていた場合に例外がスローされて見づらいエラーになる

for (const [packageName, metrics] of Object.entries(coverage)) {
  commentBody += `| Statements | ${metrics.statements.pct}% |\n`;
  // ^ metrics.statements が undefined の場合に TypeError
}

try-catch または optional chaining (metrics.statements?.pct ?? 'N/A') での防御を推奨します。


✅ 良い点

  • env 変数でのスクリプト入力REPORT_PATH / COMMENT_TITLEenv 経由で渡すことで、inputs の値が直接スクリプト中に展開されるスクリプトインジェクションを防止している。セキュリティ的に正しいアプローチです。
  • hashFiles ガードupload-artifacthashFiles('coverage/coverage-summary.json') != '' 条件を付けることで、カバレッジファイルが存在しない場合にスキップできる。
  • アクションのバージョンピニング:全アクションがバージョン固定されており、BATS テストで @main / @master 参照がないことを検証している。
  • コメントの重複防止:既存 Bot コメントを title で検索して update/create を切り替えるロジックが適切。
  • テンプレートの簡素化:80行 → 10行への圧縮でメンテナビリティが向上。

📝 Minor

  • report-jestcheckout と同様に、report-jacoco ジョブも Checkout code が本当に必要かどうか madrapps/jacoco-report のドキュメントで確認推奨(changed files 機能を使わない場合は不要な可能性あり)。
  • BATS テストはYAML 構造の静的チェックのみで、スクリプトロジックのテストは含まれていません。これは許容範囲ですが、将来的に jest スクリプト部分を外部ファイルに切り出してユニットテストを書くと品質が上がります。

優先対応: 項目 1(閾値チェックの不整合)と項目 3(不要な checkout)の修正を推奨します。項目 2 は利用者への影響が大きいため早期に対処が望ましいです。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
.github/workflows/coverage-report.yml (1)

52-81: No feedback when an unsupported format value is provided.

If a caller passes format: cobertura (or a typo), both jobs are skipped via their if conditions and the workflow completes successfully with no output or warning. This silent failure makes misconfiguration hard to diagnose.

Consider adding a validation job that fails explicitly on unsupported format values.

Example: add a validation job
  validate-inputs:
    name: Validate Inputs
    runs-on: ubuntu-latest
    steps:
      - name: Validate format
        if: inputs.format != 'jacoco' && inputs.format != 'jest'
        run: |
          echo "::error::Unsupported coverage format '${{ inputs.format }}'. Supported formats: jacoco, jest"
          exit 1

Then add needs: validate-inputs to both report jobs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/coverage-report.yml around lines 52 - 81, Add a new
validation job named validate-inputs that runs before report-jacoco and
report-jest and fails with an explicit error when inputs.format is not 'jacoco'
or 'jest' (use a step with an if: inputs.format != 'jacoco' && inputs.format !=
'jest' to echo an error and exit 1); then add needs: validate-inputs to both
report-jacoco and report-jest so they won’t run when validation fails, ensuring
unsupported or misspelled format values produce a clear failing message.
.github/workflows/templates/README.md (1)

56-101: Documentation accurately reflects the workflow design.

The usage examples, supported formats table, and feature list align well with the actual coverage-report.yml implementation. One minor note: the documentation doesn't mention that min-coverage-overall and min-coverage-changed-files thresholds are currently only enforced for the jacoco format (not jest). Consider adding a note about this limitation to avoid user confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/templates/README.md around lines 56 - 101, Update the
README entry for the reusable workflow to explicitly state that the input
parameters min-coverage-overall and min-coverage-changed-files are only enforced
for the jacoco format and are not applied when format: jest; mention this
limitation near the "Supported Formats" table or the "Features" list and
reference the coverage-report.yml inputs (min-coverage-overall,
min-coverage-changed-files) and the format names (jacoco, jest) so users know
the thresholds currently only work with jacoco.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/coverage-report.yml:
- Around line 116-128: The loop over coverage entries assumes
metrics.statements.pct, metrics.branches.pct, metrics.functions.pct, and
metrics.lines.pct always exist; modify the block that builds commentBody (where
packageName and metrics are used) to defensively handle missing shapes by either
skipping entries that don't have the expected metric objects or using safe
access with defaults (e.g., optional chaining metrics?.statements?.pct ?? 'N/A'
or a numeric 0) before interpolating; ensure you check metrics itself and each
sub-property (statements, branches, functions, lines) and update the table rows
accordingly so the code never throws when a property is missing.
- Around line 32-41: The workflow defines inputs min-coverage-overall and
min-coverage-changed-files but the Jest job never reads/enforces them; either
document this limitation or implement enforcement by adding a Jest
coverage-check step that reads those inputs and fails the job when thresholds
are not met. Concretely, update the Jest job to pass the inputs
(min-coverage-overall, min-coverage-changed-files) into the run context and add
a step (e.g., a new step named check-jest-coverage or a script in package.json
like scripts/check-jest-coverage) that parses Jest's coverage-summary
(coverage/coverage-summary.json), compares overall and changed-files coverage
against the input thresholds, and exits non-zero if any threshold is violated;
ensure the step name and input variable names match min-coverage-overall and
min-coverage-changed-files so users get expected enforcement.
- Line 111: Wrap the JSON.parse(fs.readFileSync(coveragePath, 'utf8')) call in a
try-catch to handle malformed JSON: read the file into a variable (using
fs.readFileSync(coveragePath, 'utf8')), then try to parse it into coverage with
JSON.parse; on error catch the exception and throw or log a clear, actionable
message that includes coveragePath and the original error so the step fails with
an informative message instead of an unhandled stack trace (referencing the
symbols coverage, coveragePath, fs.readFileSync, and JSON.parse).
- Around line 131-140: Replace the single-page call to
github.rest.issues.listComments with a paginated listing (use
github.paginate(github.rest.issues.listComments, {...}) or call with per_page
and iterate) so you reliably retrieve all comments before searching for an
existing bot comment; when locating the comment, stop using
comment.body.includes(title) and instead search for a unique hidden marker
(e.g., "<!-- coverage-report-marker -->") embedded in the comment body (update
where you construct the coverage comment to include that marker) and use that
marker to set botComment. Ensure references to github.rest.issues.listComments,
github.paginate, botComment, and title are updated consistently.

---

Nitpick comments:
In @.github/workflows/coverage-report.yml:
- Around line 52-81: Add a new validation job named validate-inputs that runs
before report-jacoco and report-jest and fails with an explicit error when
inputs.format is not 'jacoco' or 'jest' (use a step with an if: inputs.format !=
'jacoco' && inputs.format != 'jest' to echo an error and exit 1); then add
needs: validate-inputs to both report-jacoco and report-jest so they won’t run
when validation fails, ensuring unsupported or misspelled format values produce
a clear failing message.

In @.github/workflows/templates/README.md:
- Around line 56-101: Update the README entry for the reusable workflow to
explicitly state that the input parameters min-coverage-overall and
min-coverage-changed-files are only enforced for the jacoco format and are not
applied when format: jest; mention this limitation near the "Supported Formats"
table or the "Features" list and reference the coverage-report.yml inputs
(min-coverage-overall, min-coverage-changed-files) and the format names (jacoco,
jest) so users know the thresholds currently only work with jacoco.

Comment on lines +32 to +41
min-coverage-overall:
description: 'Minimum overall coverage threshold (%)'
required: false
type: number
default: 0
min-coverage-changed-files:
description: 'Minimum changed files coverage threshold (%)'
required: false
type: number
default: 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Jest format silently ignores min-coverage-overall and min-coverage-changed-files inputs.

The threshold inputs are defined in the workflow interface (Lines 32-41) and wired into the JaCoCo job, but the Jest job never reads or enforces them. Users setting these thresholds for Jest will get no feedback that the thresholds are being ignored.

At minimum, document this limitation. Ideally, implement threshold checking in the Jest script to fail the step when coverage falls below the configured minimum.

Also applies to: 78-157

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/coverage-report.yml around lines 32 - 41, The workflow
defines inputs min-coverage-overall and min-coverage-changed-files but the Jest
job never reads/enforces them; either document this limitation or implement
enforcement by adding a Jest coverage-check step that reads those inputs and
fails the job when thresholds are not met. Concretely, update the Jest job to
pass the inputs (min-coverage-overall, min-coverage-changed-files) into the run
context and add a step (e.g., a new step named check-jest-coverage or a script
in package.json like scripts/check-jest-coverage) that parses Jest's
coverage-summary (coverage/coverage-summary.json), compares overall and
changed-files coverage against the input thresholds, and exits non-zero if any
threshold is violated; ensure the step name and input variable names match
min-coverage-overall and min-coverage-changed-files so users get expected
enforcement.

return;
}

const coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf8'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing error handling for malformed JSON.

If coverage-summary.json exists but contains invalid JSON, JSON.parse will throw an unhandled error and the step will fail with an unhelpful stack trace. Wrap in a try-catch with a clear error message.

Proposed fix
-           const coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf8'));
+           let coverage;
+           try {
+             coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf8'));
+           } catch (e) {
+             core.setFailed(`Failed to parse coverage file at ${coveragePath}: ${e.message}`);
+             return;
+           }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf8'));
let coverage;
try {
coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf8'));
} catch (e) {
core.setFailed(`Failed to parse coverage file at ${coveragePath}: ${e.message}`);
return;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/coverage-report.yml at line 111, Wrap the
JSON.parse(fs.readFileSync(coveragePath, 'utf8')) call in a try-catch to handle
malformed JSON: read the file into a variable (using
fs.readFileSync(coveragePath, 'utf8')), then try to parse it into coverage with
JSON.parse; on error catch the exception and throw or log a clear, actionable
message that includes coveragePath and the original error so the step fails with
an informative message instead of an unhandled stack trace (referencing the
symbols coverage, coveragePath, fs.readFileSync, and JSON.parse).

Comment on lines +116 to +128
for (const [packageName, metrics] of Object.entries(coverage)) {
if (packageName === 'total') {
commentBody += '### Overall Coverage\n\n';
} else {
commentBody += `### ${packageName}\n\n`;
}

commentBody += '| Metric | Coverage |\n';
commentBody += '|--------|----------|\n';
commentBody += `| Statements | ${metrics.statements.pct}% |\n`;
commentBody += `| Branches | ${metrics.branches.pct}% |\n`;
commentBody += `| Functions | ${metrics.functions.pct}% |\n`;
commentBody += `| Lines | ${metrics.lines.pct}% |\n\n`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

No null-safety when accessing metric properties.

The code assumes every entry in the coverage JSON has statements.pct, branches.pct, functions.pct, and lines.pct. If a package entry has a different shape (e.g., missing branches), this will throw at runtime. Consider adding defensive access or skipping entries that don't match the expected schema.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/coverage-report.yml around lines 116 - 128, The loop over
coverage entries assumes metrics.statements.pct, metrics.branches.pct,
metrics.functions.pct, and metrics.lines.pct always exist; modify the block that
builds commentBody (where packageName and metrics are used) to defensively
handle missing shapes by either skipping entries that don't have the expected
metric objects or using safe access with defaults (e.g., optional chaining
metrics?.statements?.pct ?? 'N/A' or a numeric 0) before interpolating; ensure
you check metrics itself and each sub-property (statements, branches, functions,
lines) and update the table rows accordingly so the code never throws when a
property is missing.

Comment on lines +131 to +140
// Find existing coverage comment by title
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});

const botComment = comments.find(comment =>
comment.user.type === 'Bot' && comment.body.includes(title)
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Bug: listComments is paginated — existing bot comment may not be found.

github.rest.issues.listComments returns at most 30 results per page by default. On PRs with more than 30 comments, the bot comment may not be found, resulting in duplicate coverage comments instead of updating the existing one.

Use github.paginate or set per_page: 100 and paginate manually.

Also, matching by comment.body.includes(title) is fragile — a user-supplied title like "Coverage" could match unrelated comments. The standard pattern is to embed a hidden HTML marker (e.g., <!-- coverage-report-marker -->) in the comment body and search for that.

Proposed fix
+           const marker = `<!-- coverage-report: ${title} -->`;
+           commentBody = marker + '\n' + commentBody;
+
            // Find existing coverage comment by title
-           const { data: comments } = await github.rest.issues.listComments({
-             owner: context.repo.owner,
-             repo: context.repo.repo,
-             issue_number: context.issue.number,
-           });
-
-           const botComment = comments.find(comment =>
-             comment.user.type === 'Bot' && comment.body.includes(title)
-           );
+           const comments = await github.paginate(github.rest.issues.listComments, {
+             owner: context.repo.owner,
+             repo: context.repo.repo,
+             issue_number: context.issue.number,
+           });
+
+           const botComment = comments.find(comment =>
+             comment.user.type === 'Bot' && comment.body.includes(marker)
+           );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/coverage-report.yml around lines 131 - 140, Replace the
single-page call to github.rest.issues.listComments with a paginated listing
(use github.paginate(github.rest.issues.listComments, {...}) or call with
per_page and iterate) so you reliably retrieve all comments before searching for
an existing bot comment; when locating the comment, stop using
comment.body.includes(title) and instead search for a unique hidden marker
(e.g., "<!-- coverage-report-marker -->") embedded in the comment body (update
where you construct the coverage comment to include that marker) and use that
marker to set botComment. Ensure references to github.rest.issues.listComments,
github.paginate, botComment, and title are updated consistently.

Phase 2 として cobertura (.NET/Python/Go) と lcov (Istanbul/nyc/c8) の
2形式を coverage-report reusable workflow に追加。

- irongut/CodeCoverageSummary@v1.3.0 + marocchino/sticky-pull-request-comment@v2.9.4 (cobertura)
- romeovs/lcov-reporter-action@v0.4.0 (lcov)
- BATS テスト 4件追加(計15件)
- テンプレート README に使用例を追記

Closes #495

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@keito4
keito4 merged commit 982a6b6 into main Feb 21, 2026
12 checks passed
@claude

claude Bot commented Feb 21, 2026

Copy link
Copy Markdown
Contributor

Claude Code レビュー

全体的な設計(reusable workflow への抽象化、artifact ベースのレポート転送)は良好です。ただし、いくつか対処すべき問題を見つけました。


🔴 Major

1. Jestジョブでのカバレッジ閾値が機能しない(CodeRabbit指摘と同様)

min-coverage-overallmin-coverage-changed-files は inputs に定義されているにもかかわらず、report-jest ジョブで完全に無視されています。JaCoCo は madrapps/jacoco-report action が閾値チェックを内部処理しますが、Jest スクリプトには対応するロジックがありません。

修正案actions/github-scriptscript 内に追加):

// 閾値チェック
const minOverall = parseFloat(process.env.MIN_COVERAGE_OVERALL || '0');
if (minOverall > 0 && coverage.total) {
  const linesPct = coverage.total.lines.pct;
  if (linesPct < minOverall) {
    core.setFailed(`Line coverage ${linesPct}% is below minimum ${minOverall}%`);
    return;
  }
}

環境変数への追加も必要:

env:
  REPORT_PATH: ${{ inputs.report-path }}
  COMMENT_TITLE: ${{ inputs.title }}
  MIN_COVERAGE_OVERALL: ${{ inputs.min-coverage-overall }}
  MIN_COVERAGE_CHANGED_FILES: ${{ inputs.min-coverage-changed-files }}

2. コメント検索でページネーション未対応(CodeRabbit指摘と同様)

github.rest.issues.listComments はデフォルト30件のみ返します。コメント数の多いPRでは既存のカバレッジコメントが見つからず、重複コメントが発生します。

// 現状(問題あり)
const { data: comments } = await github.rest.issues.listComments({ ... });

// 修正案
const comments = await github.paginate(github.rest.issues.listComments, {
  owner: context.repo.owner,
  repo: context.repo.repo,
  issue_number: context.issue.number,
});

また、タイトルによるマッチング(comment.body.includes(title))は「Coverage」のような汎用的なタイトルで誤検知のリスクがあります。HTMLコメントマーカーの埋め込みを推奨:

const marker = `<!-- coverage-report:${title} -->`;
commentBody = `${marker}\n${commentBody}`;
// 検索時: comment.body.includes(marker)

🟡 Minor

3. report-jacoco のみ fetch-depth: 0 が設定されている

JaCoCo ジョブだけ fetch-depth: 0 があり、他の3ジョブにはありません。madrapps/jacoco-report の changed files 検出で必要なのは理解できますが、コメントで意図を明記すると可読性が上がります:

- name: Checkout code
  uses: actions/checkout@v6.0.2
  with:
    fetch-depth: 0  # JaCoCo changed-files detection requires full history

4. JSONパースとメトリクスアクセスのエラーハンドリング不足(CodeRabbit指摘と同様)

// 問題: 不正JSONでスタックトレースが出る
const coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf8'));

// 問題: metrics.statements が存在しない場合にクラッシュ
commentBody += `| Statements | ${metrics.statements.pct}% |\n`;

修正案:

let coverage;
try {
  coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf8'));
} catch (e) {
  core.setFailed(`Failed to parse coverage report at ${coveragePath}: ${e.message}`);
  return;
}

// メトリクスアクセスも安全に
commentBody += `| Statements | ${metrics.statements?.pct ?? 'N/A'}% |\n`;
commentBody += `| Branches | ${metrics.branches?.pct ?? 'N/A'}% |\n`;

5. Coberturaの閾値フォーマット確認

thresholds: '${{ inputs.min-coverage-overall }} ${{ inputs.min-coverage-changed-files }}'

irongut/CodeCoverageSummary@v1.3.0thresholds パラメータは "60 80" のスペース区切りが仕様ですが、両方とも 0(デフォルト)の場合、"0 0" を渡すとアクション内部でどう扱われるか確認が必要です(閾値チェックがスキップされるか、0%以上で常にパスするか)。


テストについて

BATSテストはYAML構造を grep で確認するアプローチで、基本的な検証として機能しています。ただし:

  • actions/github-script 内の JavaScript ロジック(JSON パース、コメント投稿)のユニットテストがないため、上記の null アクセスや閾値チェックのバグはテストで検出できません
  • Jest カバレッジレポーターのスクリプトを外部ファイルに切り出せば、単独でテスト可能になります(ただし今の規模では過剰かもしれません)

総評

設計思想(言語非依存の reusable workflow、artifact 経由でのレポート転送)は良好です。Major の2件(閾値チェック、ページネーション)は実運用で問題になる可能性が高いため、マージ前の対処を推奨します。Minor は次イテレーションでも対応可能です。

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.81.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@github-actions github-actions Bot added the released リリース済み label Feb 22, 2026
@keito4
keito4 deleted the feat/495-coverage-report-workflow branch March 1, 2026 09:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released リリース済み

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: 汎用カバレッジレポート PR コメント投稿の reusable workflow 追加

1 participant