Skip to content

Split SonarCloud workflow into two stages to support fork PRs#61

Draft
Copilot wants to merge 3 commits intomainfrom
copilot/remove-sonarcloud-workflow
Draft

Split SonarCloud workflow into two stages to support fork PRs#61
Copilot wants to merge 3 commits intomainfrom
copilot/remove-sonarcloud-workflow

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 6, 2026

SonarCloud analysis silently fails on fork PRs because pull_request workflows don't have access to secrets. The fix is a two-stage workflow split so the secret-dependent analysis runs in the base repo context via workflow_run.

Changes

  • Deleted .github/workflows/sonarcloud.yml
  • Added .github/workflows/sonarcloud-build.yml — triggers on push/pull_request, runs build + tests with coverage, serializes PR metadata (number, SHA, branch) to files, and uploads everything as a sonar-artifacts artifact. No secrets required.
  • Added .github/workflows/sonarcloud-analyze.yml — triggers via workflow_run on completion of the build workflow. Downloads the artifact, conditionally sets PR vs. push sonarscanner args, then runs begin → build → end. Has access to SONAR_TOKEN because it always executes in the base repo context.

Security

PR number and branch are passed to dotnet sonarscanner begin via environment variables (not inline ${{ }} expressions) to prevent code injection from attacker-controlled artifact content:

env:
  SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
  PR_NUMBER: ${{ steps.pr.outputs.number }}
  PR_BRANCH: ${{ steps.pr.outputs.branch }}
run: |
  dotnet sonarscanner begin \
    /d:sonar.pullrequest.key="${PR_NUMBER}" \
    /d:sonar.pullrequest.branch="${PR_BRANCH}" \
    ...
Original prompt

Context

SonarCloud does not report on fork PRs because secrets are not available in pull_request workflows from forks. The solution is to split the SonarCloud analysis into two workflows:

  1. Workflow 1 (sonarcloud-build.yml) — triggers on pull_request and push to main. Runs build, tests with coverage, and uploads the coverage XML + PR metadata as an artifact. No secrets needed.
  2. Workflow 2 (sonarcloud-analyze.yml) — triggers on workflow_run completed from workflow 1. Downloads the artifact and runs the actual SonarCloud analysis with the SONAR_TOKEN secret. Runs in base repo context so secrets are available, even for fork PRs.

What to do

1. Delete the existing .github/workflows/sonarcloud.yml

Remove this file entirely — it will be replaced by the two new workflows below.

2. Create .github/workflows/sonarcloud-build.yml

name: SonarCloud - Build & Test

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: 10.0.x

      - name: Install dotnet-coverage
        run: dotnet tool install --global dotnet-coverage

      - name: Restore dependencies
        run: dotnet restore SLNX-validator.slnx

      - name: Build
        run: dotnet build SLNX-validator.slnx --no-incremental -c Release

      - name: Test with coverage
        run: dotnet-coverage collect "dotnet test --solution SLNX-validator.slnx --no-build -c Release --verbosity normal" -f xml -o coverage.xml

      - name: Save PR metadata
        if: github.event_name == 'pull_request'
        run: |
          echo "${{ github.event.pull_request.number }}" > pr_number.txt
          echo "${{ github.event.pull_request.head.sha }}" > pr_sha.txt
          echo "${{ github.event.pull_request.head.ref }}" > pr_branch.txt

      - name: Upload SonarCloud artifacts
        uses: actions/upload-artifact@v4
        with:
          name: sonar-artifacts
          path: |
            coverage.xml
            pr_number.txt
            pr_sha.txt
            pr_branch.txt
          retention-days: 1

3. Create .github/workflows/sonarcloud-analyze.yml

name: SonarCloud - Analyze

on:
  workflow_run:
    workflows: ["SonarCloud - Build & Test"]
    types:
      - completed

jobs:
  analyze:
    runs-on: ubuntu-latest
    if: github.event.workflow_run.conclusion == 'success'
    permissions:
      contents: read
      id-token: write
      pull-requests: write

    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: ${{ github.event.workflow_run.head_sha }}

      - name: Setup .NET
        uses: actions/setup-dotnet@v4
        with:
          dotnet-version: 10.0.x

      - name: Install SonarScanner for .NET
        run: |
          dotnet tool install --global dotnet-sonarscanner
          dotnet tool install --global dotnet-coverage

      - name: Download SonarCloud artifacts
        uses: actions/download-artifact@v4
        with:
          name: sonar-artifacts
          github-token: ${{ secrets.GITHUB_TOKEN }}
          run-id: ${{ github.event.workflow_run.id }}

      - name: Read PR metadata
        id: pr
        run: |
          if [ -f pr_number.txt ]; then
            echo "number=$(cat pr_number.txt)" >> $GITHUB_OUTPUT
            echo "sha=$(cat pr_sha.txt)" >> $GITHUB_OUTPUT
            echo "branch=$(cat pr_branch.txt)" >> $GITHUB_OUTPUT
            echo "is_pr=true" >> $GITHUB_OUTPUT
          else
            echo "is_pr=false" >> $GITHUB_OUTPUT
          fi

      - name: Restore dependencies
        run: dotnet restore SLNX-validator.slnx

      - name: Begin SonarCloud analysis (PR)
        if: steps.pr.outputs.is_pr == 'true'
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
        run: |
          dotnet sonarscanner begin \
            /k:"slnx-validator" \
            /o:"304notmodified" \
            /d:sonar.token="${SONAR_TOKEN}" \
            /d:sonar.host.url="https://sonarcloud.io" \
            /d:sonar.cs.vscoveragexml.reportsPaths="coverage.xml" \
            /d:sonar.pullrequest.key="${{ steps.pr.outputs.number }}" \
            /d:sonar.pullrequest.branch="${{ steps.pr.outputs.branch }}" \
            /d:sonar.pullrequest.base="main"

      - name: Begin SonarCloud analysis (push)
        if: steps.pr.outputs.is_pr == 'false'
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
        run: |
          dotnet sonarscanner begin \
            /k:"slnx-validator" \
            /o:"304notmodified" \
            /d:sonar.token="${SONAR_TOKEN}" \
            /d:sonar.host.url="https://sonarcloud.io" \
            /d:sonar.cs.vscoveragexml.reportsPaths="coverage.xml"

      - name: Build
        run: dotnet...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

*This pull request was created from Copilot chat.*
>

Comment on lines +19 to +24
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.workflow_run.head_sha }}

- name: Setup .NET
Copilot AI changed the title [WIP] Remove existing sonarcloud workflow and add new analysis workflows Split SonarCloud workflow into two stages to support fork PRs Apr 6, 2026
Copilot AI requested a review from 304NotModified April 6, 2026 10:28
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.

3 participants