Skip to content

fix(ci): Skip the coverage comment on pull requests from forks - #901

Merged
stefanko-ch merged 2 commits into
mainfrom
fix/coverage-comment-on-forks
Sep 19, 2026
Merged

stefanko-ch merged 2 commits into
mainfrom
fix/coverage-comment-on-forks

Conversation

@stefanko-ch

@stefanko-ch stefanko-ch commented Sep 19, 2026 •

Copy link
Copy Markdown
Owner

Refs #847.

What this fixes

A workflow run for a pull request from a fork gets a read-only token, whatever permissions: declares. The coverage-comment step therefore cannot post, and fails with:

HttpError: Resource not accessible by integration

two steps after pytest unit reported success. So every external contribution has shown a red Test (pytest + coverage) check while its tests passed.

On #867 that is precisely what happened, and it cost the contributor a round of looking for a fault that was not his — the job's name is what people read, not its step list:

Step Result
6. pytest unit success
7. Coverage comment on PR failure
8. Upload coverage to Codecov success

The change

The step now also requires the pull request to come from this repository:

if: >-
  github.event_name == 'pull_request'
  && github.event.pull_request.head.repo.full_name == github.repository

Nothing is lost for forks: the Codecov upload and the coverage artifact are separate steps and still run. What goes away is a red check that never meant anything.

Guards

Two, both mutation-tested:

  • one walks every workflow and fails when a step whose uses: is a known PR-writing action lacks the fork exemption — keyed on the action, not the step name, because a name is free text;
  • one fails when that watch list matches nothing at all, which is how the first would quietly pass the day the action is replaced.

Removing the exemption fails the first; renaming the action fails the second.

Relation to #847

#847 is about a reviewer that fails its quota and still reports green. This is the mirror image — a step that reports red without anything being wrong — and the same underlying problem: a check whose colour does not mean what a reader takes it to mean. It does not close #847.

pytest tests/unit: 3691 passed. Pre-commit (incl. actionlint): all hooks pass.

Local CodeRabbit round

Reviewed 3f9e08ec: 0 findings.

Summary by Sourcery

Skip pull-request coverage comments from forks while preserving coverage uploads and artifacts.

Bug Fixes:

  • Prevent coverage-comment failures from marking fork-originated pull requests as failed when their tests pass.

Enhancements:

  • Add mutation-resistant workflow checks that ensure pull-request-writing actions require same-repository pull requests and remain covered by the guard tests.

Tests:

  • Add workflow expression tests that verify fork exemptions for configured pull-request-writing actions and fail if the action watch list becomes stale.

A workflow run for a pull request from a fork gets a read-only token,
whatever `permissions:` declares. The coverage-comment step therefore
cannot post, and fails the job with

  HttpError: Resource not accessible by integration

two steps after `pytest unit` reported success. So every external
contribution has shown a red `Test (pytest + coverage)` check while its
tests passed. On #867 that read as the contributor's tests failing, and
cost a round of looking for a fault that was not his — the job's name is
what people read, not its step list.

The step now also requires the pull request to come from this repository.
Nothing is lost for forks: the Codecov upload and the coverage artifact
are separate steps and still run.

Two guards. One walks every workflow and fails when a step whose `uses:`
is a known PR-writing action lacks the fork exemption — keyed on the
action rather than the step name, because a name is free text. The other
fails when that watch list matches nothing at all, which is how the first
would quietly pass the day the action is replaced.

Both mutation-tested: removing the exemption fails the first, renaming
the action fails the second.

Refs #847
@sourcery-ai

sourcery-ai Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

The PR prevents fork-originated pull requests from producing misleading failures by skipping the PR-writing coverage comment when the workflow token is read-only, while preserving coverage reporting and adding tests that guard the workflow condition and its action watch list.

File-Level Changes

Change Details Files
Restrict the PR coverage-comment action to same-repository pull requests so fork workflows do not fail on an unwritable token.
  • Add a repository-origin check to the step condition while retaining normal pull-request gating.
  • Keep Codecov upload and coverage artifact steps unaffected for fork contributions.
.github/workflows/python-tests.yml
Add mutation-resistant workflow tests that enforce fork exemptions for known PR-writing actions and verify the action watch list remains active.
  • Scan all workflow steps by uses: action rather than free-text step names.
  • Assert each watched PR-writing action includes the same-repository expression.
  • Fail if the watch list matches no workflow step.
tests/unit/test_workflow_expressions.py

Assessment against linked issues

Issue Objective Addressed Explanation
#847 Add a CI check or other visible mechanism that detects known reviewer quota-limit messages and reports failure or a warning instead of allowing the reviewer check to remain green. ❌ The PR only changes the coverage-comment workflow step to skip pull requests from forks. It does not inspect reviewer comments, detect quota-limit strings, or alter reviewer check status.
#847 Ensure review-status automation considers review bodies and issue comments, rather than relying only on line comments or green check states, so a missing review cannot be mistaken for a successful review. ❌ The changes do not modify any review-fetching or review-validation logic and do not add checks for pull request reviews or issue comments.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Sep 19, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 53 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: stefanko-ch/Nexus-Stack/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 1ae921e7-4fab-4d12-8b9e-c17a71f86cd0

📥 Commits

Reviewing files that changed from the base of the PR and between b00d1b9 and e7ee7cb.

📒 Files selected for processing (2)
  • .github/workflows/python-tests.yml
  • tests/unit/test_workflow_expressions.py

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.

@sourcery-ai sourcery-ai 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.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="tests/unit/test_workflow_expressions.py" line_range="572-576" />
<code_context>
+    for step in _steps_with_uses(workflow):
+        if not any(str(step["uses"]).startswith(a) for a in _PR_WRITING_ACTIONS):
+            continue
+        condition = str(step.get("if", ""))
+        assert _SAME_REPO in condition, (
+            f"{path.name}: step {step.get('name')!r} writes to the pull request but "
+            "does not exempt forks, so it fails the job on every external contribution"
</code_context>
<issue_to_address>
**issue (testing):** The guard only checks that the textual `_SAME_REPO` fragment appears somewhere in the condition, so replacing the workflow's `&&` with `||` still passes the test while forked pull requests execute the write action and fail with the read-only-token error.

**Triggers:** When the workflow condition is changed in a way that preserves the comparison text but changes its boolean semantics.

**Suggested fix:** Assert the complete expression semantics, including the `pull_request` event check and conjunction, rather than only checking for a substring; for example, parse or normalize the condition and explicitly reject `||`/unconditional branches.

```suggestion
        condition = " ".join(str(step.get("if", "")).split())
        assert condition == (
            "github.event_name == 'pull_request' && "
            f"{_SAME_REPO}"
        ), (
            f"{path.name}: step {step.get('name')!r} writes to the pull request but "
            "does not exempt forks, so it fails the job on every external contribution"
        )
```
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: tests/unit/test_workflow_expressions.py:576


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread tests/unit/test_workflow_expressions.py Outdated
@github-actions

github-actions Bot commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

coverage

Coverage report — nexus_deploy
FileStmtsMissCoverMissing
__init__.py50100% 
_remote.py420100% 
cli.py40100% 
compose_restart.py400100% 
compose_runner.py880100% 
config.py1810100% 
firewall.py2060100% 
forgejo.py5985590%783–784, 789, 812–813, 825–826, 862–863, 875–876, 894–895, 920–921, 943–944, 955–956, 1011–1012, 1020–1021, 1026, 1032–1033, 1057–1058, 1091–1092, 1095, 1126–1127, 1168–1169, 1174–1175, 1215–1216, 1247–1248, 1271–1272, 1277–1278, 1377–1378, 1383–1384, 1860, 1864, 1885, 1913–1914, 2001
forgejo_runner.py47197%228
hetzner_capacity.py1720100% 
hetzner_snapshot.py2020100% 
infisical.py2220100% 
kestra.py177398%227, 441, 802
orchestrator.py6867788%205, 504–505, 517, 618, 810, 822, 992–993, 998–999, 1031–1033, 1042, 1047–1049, 1060, 1097–1098, 1103–1104, 1124, 1159–1160, 1165–1166, 1174, 1199–1200, 1208, 1279–1280, 1285–1286, 1338–1339, 1344–1345, 1596, 1599, 1669, 1675–1676, 1681–1682, 1716, 1840–1841, 1846–1847, 1896–1897, 1902–1903, 1962, 1977, 2034, 2039–2040, 2045–2046, 2053, 2059, 2234, 2241, 2253–2254, 2259–2260, 2266, 2272, 2356–2357, 2378–2379
pg_preflight.py191199%214
pipeline.py2361394%166–167, 351, 389, 470, 492, 587–588, 633–634, 724–725, 772
r2_tokens.py113298%87, 150
s3_persistence.py200199%315
s3_restore.py1030100% 
secret_sync.py990100% 
seeder.py980100% 
service_env.py5513394%2072, 2074–2076, 2084–2085, 2660–2663, 2668–2674, 2741–2745, 2761–2765, 2789, 2791, 2813–2814, 2821, 2946
services.py361199%2921
setup.py1651392%245, 315–318, 326, 330–335, 351
ssh.py520100% 
stack_sync.py960100% 
tfvars.py440100% 
tofu.py860100% 
workspace_coords.py1010100% 
TOTAL516620096% 

@codecov

codecov Bot commented Sep 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Address PR review comments on #901.

[4052815422] sourcery-ai — Fixed. The guard checked that the fork
comparison appeared somewhere in the condition, and `A || B` contains the
same text as `A && B`. So a workflow whose `&&` had been swapped for `||`
would have passed the test and still run the step for every fork, which
is the exact failure the guard exists to prevent — the substring was
evidence of the right words, not of the right meaning.

It now normalises the whitespace, rejects `||` outright, and requires the
fork check to be conjoined with `&&`.

Mutation-tested: swapping the workflow condition to `||` fails
test_a_step_that_comments_on_the_pr_is_skipped_for_forks.

@sourcery-ai sourcery-ai 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.

Sourcery assessment

Approved.

@stefanko-ch
stefanko-ch merged commit 90ef3b2 into main Sep 19, 2026
14 checks passed
@stefanko-ch
stefanko-ch deleted the fix/coverage-comment-on-forks branch September 19, 2026 10:54
stefanko-ch pushed a commit that referenced this pull request Sep 25, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.83.0](v0.82.3...v0.83.0)
(2026-09-25)


### 🚀 Features

* **stacks:** Add Cube as the semantic layer over the warehouse
([#905](#905))
([2dc7a6e](2dc7a6e))


### 🐛 Bug Fixes

* **ci:** Skip the coverage comment on pull requests from forks
([#901](#901))
([90ef3b2](90ef3b2))
* **deploy:** Hash the Filestash password without htpasswd
([#900](#900))
([b67f1c5](b67f1c5))


### 🔧 Maintenance

* **ci:** Remove the duplicate orphan-cleanup workflow, keep the tool
([#902](#902))
([04d7885](04d7885))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

## Summary by Sourcery

Release version 0.83.0 with Cube integration, CI and deployment fixes,
and workflow maintenance.

New Features:
- Add Cube as a semantic layer over the warehouse.

Bug Fixes:
- Skip coverage comments for pull requests originating from forks.
- Hash Filestash passwords without relying on htpasswd.

CI:
- Remove the duplicate orphan-cleanup workflow while retaining the
cleanup tool.

Chores:
- Release version 0.83.0 and update the changelog and release manifest.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

docs(deploy): A reviewer that fails its quota still reports a green check

1 participant