actions / actions/upload-code-coverage

Push to default branch attributes coverage to an unrelated fork PR with a same-named head branch

Open
#14 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
99
Forks
20
PR merge metrics
No merged PRs in 30d

Description

Summary

On push events, the action associates an open PR via gh pr list --head "${{ github.ref_name }}". That matches by head-branch name across all repositories, so an open cross-repo (fork) PR whose head branch happens to be named the same as the pushed branch gets selected. Since upload_coverage.py prefers pull_request_number over ref, the push's coverage is filed under that unrelated PR instead of the branch, and the branch baseline is never recorded.

Impact

This reliably breaks the default branch baseline: if any contributor has an open PR from their fork's main branch (head branch main), every push to your main is attributed to that PR. The default-branch coverage never populates, so every other PR's coverage comment shows "Coverage data for the main branch is not yet available."

Root cause

action.yml (push path):

else
  COMMIT_OID="${{ github.sha }}"
  REF="${{ github.ref }}"
  PR_NUMBER=$(gh pr list \
    --repo "$GITHUB_REPOSITORY" \
    --head "${{ github.ref_name }}" \   # not scoped to the base repo's own head branches
    --state open \
    --json number \
    --jq '.[0].number // empty' 2>/dev/null || true)
fi

--repo only selects which repository's PR list to query; a fork PR is still part of that list (it targets the base repo, only its head branch lives in the fork). --head <branch> matches the head-branch name regardless of where the head lives, so a fork PR with head branch <branch> is returned. Then upload_coverage.py:

if pr_number:
    payload["pull_request_number"] = int(pr_number)
elif ref:
    payload["ref"] = ref

pull_request_number wins, so the ref (the branch) is dropped from the payload.

Reproduction
  1. The repo has an open PR from a fork whose head branch is named main (head someuser:mainyou:main).
  2. A push to main (the default branch) triggers the upload.
  3. The action log shows:
Upload parameters
  commit_oid: <sha>
  ref: refs/heads/main
  pr_number: <the unrelated fork PR number>
Coverage report uploaded successfully.
  1. Coverage is attributed to that PR; the main baseline stays empty and PRs report "Coverage data for the main branch is not yet available."

Confirmed that gh pr list --repo OWNER/REPO --head main --state open returns the fork PR (isCrossRepository: true), so the existing --repo argument does not prevent this.

Suggested fix

Scope the PR lookup to head branches in the base repository itself, so fork PRs aren't matched:

           COMMIT_OID="${{ github.sha }}"
           REF="${{ github.ref }}"
+          REPO_OWNER="${GITHUB_REPOSITORY%%/*}"
+          REPO_NAME="${GITHUB_REPOSITORY#*/}"
           PR_NUMBER=$(gh pr list \
             --repo "$GITHUB_REPOSITORY" \
             --head "${{ github.ref_name }}" \
             --state open \
-            --json number \
-            --jq '.[0].number // empty' 2>/dev/null || true)
+            --json number,headRepositoryOwner,headRepository \
+            --jq "[.[] | select(.headRepositoryOwner.login == \"$REPO_OWNER\" and .headRepository.name == \"$REPO_NAME\")] | .[0].number // empty" 2>/dev/null || true)

Alternatives: skip PR association entirely when the pushed ref is the default branch, or prefer ref over pull_request_number for push events.

Drafted by Claude (Anthropic AI assistant).

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in the push path of action.yml and inspect upload_coverage.py's precedence between pull_request_number and ref. Reproduce the issue with an open fork PR whose head branch matches the pushed branch, then verify the lookup no longer selects that fork. Done when the push remains attributed to its branch and the default-branch coverage baseline is recorded.

Written by the indexing model from the issue text.

Assessment

Tech stack
github-actions, python, shell
Domain
ci-cd, testing-qa
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.