Skip to content

fix(claude-ci-fix): resolve PR via API when check_run payload is empty#157

Merged
don-petry merged 2 commits intomainfrom
fix/claude-ci-fix-pr-resolution
Apr 21, 2026
Merged

fix(claude-ci-fix): resolve PR via API when check_run payload is empty#157
don-petry merged 2 commits intomainfrom
fix/claude-ci-fix-pr-resolution

Conversation

@don-petry
Copy link
Copy Markdown
Contributor

@don-petry don-petry commented Apr 21, 2026

Problem

The claude-ci-fix job was not triggering on CI failures. Three bugs combined to prevent it:

  1. check_run.pull_requests is often empty — GitHub's webhook payload frequently omits the pull_requests array for external checks (SonarCloud, CodeQL, status checks). The pull_requests[0] != null guard in the if condition silently skipped every such event.

  2. Self-exclusion name filter had wrong case — the filter used 'claude-code / claude' (lowercase) but GitHub names check runs using the workflow's name: field, which is "Claude Code". The filter never matched, meaning Claude's own check runs could re-trigger the fix job.

  3. Concurrency key referenced pull_requests[0].number — evaluated at workflow scheduling time, before the job runs. When the payload array is empty this produces a null key, breaking concurrency deduplication.

Fix

  • Remove the pull_requests[0] != null guard from the if condition
  • Add a Resolve PR number step that uses the payload value when present, and falls back to the /commits/{sha}/pulls API endpoint when not
  • Gate the checkout and run-claude steps on steps.pr.outputs.number != ''
  • Fix the self-exclusion filter to !startsWith(..., 'Claude Code')
  • Fix the concurrency key to use head_sha

Summary by CodeRabbit

  • Chores
    • Improved CI/CD workflow robustness for handling pull request checks with enhanced fallback mechanisms and SHA-based concurrency management.

- Remove pull_requests[0] != null guard from if condition; GitHub
  frequently omits this array in check_run webhook payloads for
  external checks (SonarCloud, CodeQL, etc.)
- Add Resolve PR number step that falls back to the commits/{sha}/pulls
  API when the payload's pull_requests array is empty
- Fix self-exclusion name filter: was 'claude-code / claude' (wrong
  case); actual check run names start with 'Claude Code'
- Fix concurrency key: was referencing pull_requests[0].number which
  is null when payload is empty; now uses head_sha
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

Warning

Rate limit exceeded

@don-petry has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 38 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 47 minutes and 38 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b07aa448-317e-4f27-8e33-9185f172b240

📥 Commits

Reviewing files that changed from the base of the PR and between edab4fd and 11aefeb.

📒 Files selected for processing (2)
  • scripts/compliance-audit.sh
  • standards/ci-standards.md
📝 Walkthrough

Walkthrough

Modified .github/workflows/claude-code-reusable.yml to improve PR number resolution with a fallback mechanism. Switched concurrency grouping from PR-number-based to SHA-based. Added conditional gating of downstream steps based on successful PR number resolution.

Changes

Cohort / File(s) Summary
CI Workflow Enhancement
.github/workflows/claude-code-reusable.yml
Introduced new "Resolve PR number" step with fallback logic to query open PRs by commit SHA if check run PR data is unavailable. Changed concurrency group to use head_sha. Updated job condition to exclude Claude Code check runs. Gated checkout and build steps on resolved PR number availability.

Sequence Diagram(s)

sequenceDiagram
    participant GitHub as GitHub Event
    participant Workflow as Workflow Engine
    participant ResolvePR as Resolve PR Number
    participant GH_API as GitHub API
    participant Checkout as Checkout Repository
    participant RunCode as Run Claude Code

    GitHub->>Workflow: Trigger check_run event
    Workflow->>ResolvePR: Execute step
    ResolvePR->>ResolvePR: Check pull_requests[0].number
    alt PR number available
        ResolvePR->>ResolvePR: Use PR number
    else PR number empty
        ResolvePR->>GH_API: Query for open PRs by head_sha
        GH_API-->>ResolvePR: Return PR number
    end
    ResolvePR-->>Workflow: Output PR number
    
    alt PR number resolved
        Workflow->>Checkout: Proceed (gated on PR number)
        Checkout->>Checkout: Checkout repository
        Workflow->>RunCode: Proceed (gated on PR number)
        RunCode->>RunCode: Run Claude Code
    else PR number not resolved
        Workflow->>Workflow: Skip downstream steps
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: fixing PR resolution in the claude-ci-fix workflow when the check_run payload is empty by using an API fallback.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/claude-ci-fix-pr-resolution

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/claude-code-reusable.yml:
- Around line 58-60: The workflow currently keys concurrency on
github.event.check_run.head_sha which prevents coalescing runs for the same PR
and can cause duplicate Claude fix runs; implement a two-job split: add a
lightweight resolve-pr job (use github.event.check_run.pull_requests[0].number
and fallback to querying the repo commits/<head_sha>/pulls) that emits an output
like number, then change the claude-ci-fix job to depend on needs.resolve-pr and
set concurrency.group to claude-ci-fix-${{ needs.resolve-pr.outputs.number }}
with cancel-in-progress: true, falling back to head_sha only when the resolve-pr
output is empty (use the resolve job output to decide the concurrency key).
- Around line 75-82: The script treats check_run.pull_requests[0].number as
authoritative but only filters for open PRs in the fallback gh api path
(select(.state == "open")), causing asymmetric behavior; update the fast path
that uses PR="${{ github.event.check_run.pull_requests[0].number }}" to validate
the PR state via the GitHub API (or call gh api repos/.../pulls/$PR and check
.state == "open") before assigning PR, so both the direct-payload and fallback
branches perform the same "open" check and only set/echo number=$PR when the PR
is still open.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 83854682-2f86-4269-b38f-ffbfcea0cf3d

📥 Commits

Reviewing files that changed from the base of the PR and between 3a467f4 and edab4fd.

📒 Files selected for processing (1)
  • .github/workflows/claude-code-reusable.yml

Comment on lines 58 to 60
concurrency:
group: ${{ github.event.check_run.pull_requests[0] && format('claude-ci-fix-pr-{0}', github.event.check_run.pull_requests[0].number) || format('claude-ci-fix-run-{0}', github.run_id) }}
group: claude-ci-fix-${{ github.event.check_run.head_sha }}
cancel-in-progress: true
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

Concurrency now keyed per-commit — enables parallel Claude runs on the same PR.

Switching from pull_requests[0].number to head_sha fixes the null-key bug, but removes per-PR coalescing. If two failing checks report against different SHAs of the same PR (slow external checks after a push, or multiple pushes in quick succession), both workflows land in distinct concurrency groups, cancel-in-progress does nothing, and two Claude instances will concurrently check out the PR branch and push fixes — producing push races and duplicate/conflicting fix commits.

Consider resolving the PR number before concurrency evaluation and keying on it when available. Since workflow-level concurrency cannot depend on step outputs, one option is to move PR resolution into a lightweight preceding job and use needs.<job>.outputs.pr for the fix job's concurrency group, falling back to head_sha only when no PR is found:

♻️ Sketch: two-job split so concurrency can key on PR number
jobs:
  resolve-pr:
    if: github.event_name == 'check_run' && github.event.check_run.conclusion == 'failure' &&
        !startsWith(github.event.check_run.name, 'Claude Code')
    runs-on: ubuntu-latest
    outputs:
      number: ${{ steps.pr.outputs.number }}
    steps:
      - id: pr
        env:
          GH_TOKEN: ${{ secrets.GH_PAT_WORKFLOWS || github.token }}
        run: |
          PR="${{ github.event.check_run.pull_requests[0].number }}"
          if [ -z "$PR" ]; then
            PR=$(gh api \
              "repos/${{ github.repository }}/commits/${{ github.event.check_run.head_sha }}/pulls" \
              --jq '[.[] | select(.state == "open")] | first | .number // empty')
          fi
          echo "number=$PR" >> "$GITHUB_OUTPUT"

  claude-ci-fix:
    needs: resolve-pr
    if: needs.resolve-pr.outputs.number != ''
    concurrency:
      group: claude-ci-fix-${{ needs.resolve-pr.outputs.number }}
      cancel-in-progress: true
    ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/claude-code-reusable.yml around lines 58 - 60, The
workflow currently keys concurrency on github.event.check_run.head_sha which
prevents coalescing runs for the same PR and can cause duplicate Claude fix
runs; implement a two-job split: add a lightweight resolve-pr job (use
github.event.check_run.pull_requests[0].number and fallback to querying the repo
commits/<head_sha>/pulls) that emits an output like number, then change the
claude-ci-fix job to depend on needs.resolve-pr and set concurrency.group to
claude-ci-fix-${{ needs.resolve-pr.outputs.number }} with cancel-in-progress:
true, falling back to head_sha only when the resolve-pr output is empty (use the
resolve job output to decide the concurrency key).

Comment on lines +75 to +82
run: |
PR="${{ github.event.check_run.pull_requests[0].number }}"
if [ -z "$PR" ]; then
PR=$(gh api \
"repos/${{ github.repository }}/commits/${{ github.event.check_run.head_sha }}/pulls" \
--jq '[.[] | select(.state == "open")] | first | .number // empty')
fi
echo "number=$PR" >> "$GITHUB_OUTPUT"
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

PR state not verified on the fast path — asymmetric with the API fallback.

When check_run.pull_requests[0].number is present in the payload, the script accepts it as-is; when falling back to the API, only open PRs are kept (select(.state == "open")). A failing check_run that arrives after the PR was merged/closed will trigger Claude on the fast path but skip on the fallback path. Consider treating the payload number as a hint and validating state uniformly, e.g.:

🛡️ Proposed fix
         run: |
           PR="${{ github.event.check_run.pull_requests[0].number }}"
-          if [ -z "$PR" ]; then
-            PR=$(gh api \
-              "repos/${{ github.repository }}/commits/${{ github.event.check_run.head_sha }}/pulls" \
-              --jq '[.[] | select(.state == "open")] | first | .number // empty')
-          fi
+          if [ -n "$PR" ]; then
+            STATE=$(gh api "repos/${{ github.repository }}/pulls/$PR" --jq '.state' 2>/dev/null || echo "")
+            [ "$STATE" = "open" ] || PR=""
+          fi
+          if [ -z "$PR" ]; then
+            PR=$(gh api \
+              "repos/${{ github.repository }}/commits/${{ github.event.check_run.head_sha }}/pulls" \
+              --jq '[.[] | select(.state == "open")] | first | .number // empty')
+          fi
           echo "number=$PR" >> "$GITHUB_OUTPUT"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/claude-code-reusable.yml around lines 75 - 82, The script
treats check_run.pull_requests[0].number as authoritative but only filters for
open PRs in the fallback gh api path (select(.state == "open")), causing
asymmetric behavior; update the fast path that uses PR="${{
github.event.check_run.pull_requests[0].number }}" to validate the PR state via
the GitHub API (or call gh api repos/.../pulls/$PR and check .state == "open")
before assigning PR, so both the direct-payload and fallback branches perform
the same "open" check and only set/echo number=$PR when the PR is still open.

- Document the third job (claude-ci-fix) in ci-standards.md section 4:
  update jobs list, triggers example, and checkout requirement note
- Extend check_claude_workflow_checkout() to also verify the check_run
  trigger is present — without it claude-ci-fix can never fire
@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant