ci(api-review-gate): publish the gate as a commit status - #3440
myasnikovdaniil wants to merge 2 commits into
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
📝 WalkthroughWalkthroughThe workflows now isolate fork pull-request execution from same-repository execution. Fork builds export OCI archives for privileged workflows. Separate jobs publish ChangesFork workflow isolation
Estimated code review effort: 5 (Critical) | ~90 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/api-review-gate.yaml:
- Around line 41-43: Update the workflow’s concurrency group so non-actionable
pull_request_review events use a distinct group, preventing skipped runs from
cancelling the active pull_request gate. Preserve the existing shared group for
actionable events and keep the job condition around github.event_name and
github.event.review.state unchanged.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: d12930e4-b50a-41ca-9c1b-8de396135fd9
📒 Files selected for processing (1)
.github/workflows/api-review-gate.yaml
Review summaryThe noise problem this PR fixes is real, and the intent is right. But the safety argument in the PR description only covers the case where the push-anchor run passed; it does not cover the case where the anchor run failed — which is exactly the case the gate exists to enforce. Flagging one blocking-severity concern to verify before merge. [CRITICAL — please verify] A
|
The gate's merge context was its job name, which inherits two GitHub behaviours that together make it both unenforceable and self-poisoning. A required *job* that skips counts as PASSED by branch protection, so any path that skips the evaluating job hands out a free pass — the hole that let fork PRs merge with e2e never run (#3257). And check runs are not deduplicated per name: every workflow run gets its own check suite, so `GET /commits/{sha}/check-runs?filter=latest` returns the same set as `filter=all`, and the rollup is worst-of. Two runs on one head SHA therefore leave two verdicts standing for one context name, and a cancelled run poisons the context until the next push. Commit statuses are deduplicated per context, newest wins. Rename the evaluating job off the gate context and publish "API Review Gate" as a commit status: `api-gate-report` for same-repo PRs, and a privileged workflow_run for forks, whose GITHUB_TOKEN is read-only on both the pull_request and the pull_request_review trigger and so can never post a status itself. Skipped and cancelled runs post nothing, so declining to post can only ever preserve a verdict some real run reached — it can never invent one. That invariant is what makes it safe to stop re-evaluating on the plain `commented` reviews the review bots post: such a review cannot move a verdict that counts only APPROVED reviews, and the run that skips now leaves the previous status untouched instead of overwriting it. Also split the concurrency group so a run that computes no verdict cannot cancel one that does. A bot commenting during the ~30s evaluation used to cancel it and then contribute nothing, leaving a cancelled run as that SHA's only api-gate verdict (41d1153, 4b1966c). For the gate to actually block a merge, main branch protection must require the "API Review Gate" status context; until then it is advisory, as the job name was. Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
The "E2E Tests" commit-status pattern in §10 now has a second consumer in api-review-gate.yaml, and the two GitHub behaviours that make it load-bearing were not written down anywhere. Check runs are not deduplicated per name across workflow runs — each run gets its own check suite, so filter=latest returns the same set as filter=all and the aggregate is worst-of, meaning a cancelled run poisons a context until the next push — while commit statuses are deduplicated per context with the newest winning. And the fork read-only token restriction covers pull_request_review as well as pull_request, so an approval on a fork PR cannot publish a status either, which is why both gates need a privileged workflow_run half rather than only handling the push path. Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
f44eedf to
179d59a
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
packages/core/talos/Makefile (1)
17-17: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winConsider removing a stale archive before the copy.
skopeo copyto an existingoci-archive:destination can fail or append a second manifest, which then breaks the single-image assumption ine2e-fork.yaml. A repeatedmake image-talosin the same workspace hits this. Add anrm -fbefore the copy.♻️ Proposed change
- $(if $(strip $(OCI_EXPORT_DIR)),mkdir -p "$(OCI_EXPORT_DIR)"; skopeo copy "$$SRC" "oci-archive:$(OCI_EXPORT_DIR)/talos.oci.tar:$(IMAGE_TAG)",skopeo copy "$$SRC" docker://$(REGISTRY)/talos:$(IMAGE_TAG)); \ + $(if $(strip $(OCI_EXPORT_DIR)),mkdir -p "$(OCI_EXPORT_DIR)"; rm -f "$(OCI_EXPORT_DIR)/talos.oci.tar"; skopeo copy "$$SRC" "oci-archive:$(OCI_EXPORT_DIR)/talos.oci.tar:$(IMAGE_TAG)",skopeo copy "$$SRC" docker://$(REGISTRY)/talos:$(IMAGE_TAG)); \🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/talos/Makefile` at line 17, Update the OCI export branch in the Makefile command to remove the existing talos.oci.tar archive with rm -f immediately before invoking skopeo copy, while leaving the registry-copy branch unchanged.hack/common-envs_test.bats (1)
24-24: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winPass
COZYSTACK_VERSION=0in these three tests to keep them hermetic.
hack/common-envs.mkruns a version probe at parse time. WhenCOZYSTACK_VERSIONis empty, it executesgit remote add upstream …andgit fetch upstream --tags(lines 128-132). Tests 4 and 5 already setCOZYSTACK_VERSION=0, ande2e-fork.yamlsets it for the same reason. Tests 1-3 omit it, so a shallow or tagless checkout makes them mutate git remotes and reach the network.♻️ Proposed change
- out=$(make -n -C packages/system/cozystack-controller image OCI_EXPORT_DIR=/tmp/ocitest IMAGE_TAG=pr-1-abc BUILDER=b) + out=$(make -n -C packages/system/cozystack-controller image OCI_EXPORT_DIR=/tmp/ocitest IMAGE_TAG=pr-1-abc COZYSTACK_VERSION=0 BUILDER=b)Apply the same addition to the
capi-providers-cpproviderandtalosinvocations.Also applies to: 37-37, 51-51
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@hack/common-envs_test.bats` at line 24, Set COZYSTACK_VERSION=0 on the three hermetic make invocations in hack/common-envs_test.bats, including the shown system/cozystack-controller command and the capi-providers-cpprovider and talos invocations. Preserve the existing arguments and command structure while preventing parse-time version probing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/api-review-gate-fork.yaml:
- Around line 44-67: Bind fork status publication to the triggering pull
request: in .github/workflows/api-review-gate-fork.yaml lines 44-67, grant
pull-requests: read, query pull requests associated with HEAD_SHA, and publish
nothing unless an open request has head.sha === HEAD_SHA and head.repo.full_name
matching github.event.workflow_run.head_repository.full_name; in
.github/workflows/e2e-fork.yaml lines 143-152, extend the existing
assoc.data.find predicate with those same SHA and repository checks so all
decisions and status targets use the triggering fork’s pull request.
In @.github/workflows/e2e-fork.yaml:
- Around line 304-322: Update the “Derive trusted image allowlist (base tree)”
step so cozystack-operator is always included, matching the installer targets
invoked by the fork build even when image-operator’s prereq output is skipped.
Prefer deriving the name from the same installer/image targets; otherwise
explicitly add cozystack-operator to _allow.txt while preserving the existing
archive-derived entries and validation.
In `@docs/agents/e2e-testing.md`:
- Line 103: Update the section 10 heading to refer to the “E2E Tests” commit
status instead of a check-run, keeping the existing same-repo versus fork
workflow wording unchanged.
---
Nitpick comments:
In `@hack/common-envs_test.bats`:
- Line 24: Set COZYSTACK_VERSION=0 on the three hermetic make invocations in
hack/common-envs_test.bats, including the shown system/cozystack-controller
command and the capi-providers-cpprovider and talos invocations. Preserve the
existing arguments and command structure while preventing parse-time version
probing.
In `@packages/core/talos/Makefile`:
- Line 17: Update the OCI export branch in the Makefile command to remove the
existing talos.oci.tar archive with rm -f immediately before invoking skopeo
copy, while leaving the registry-copy branch unchanged.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 11484404-e518-44c5-b22f-068c661b9230
📒 Files selected for processing (8)
.github/workflows/api-review-gate-fork.yaml.github/workflows/api-review-gate.yaml.github/workflows/e2e-fork.yaml.github/workflows/pull-requests.yamldocs/agents/e2e-testing.mdhack/common-envs.mkhack/common-envs_test.batspackages/core/talos/Makefile
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 3
🧹 Nitpick comments (2)
packages/core/talos/Makefile (1)
17-17: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winConsider removing a stale archive before the copy.
skopeo copyto an existingoci-archive:destination can fail or append a second manifest, which then breaks the single-image assumption ine2e-fork.yaml. A repeatedmake image-talosin the same workspace hits this. Add anrm -fbefore the copy.♻️ Proposed change
- $(if $(strip $(OCI_EXPORT_DIR)),mkdir -p "$(OCI_EXPORT_DIR)"; skopeo copy "$$SRC" "oci-archive:$(OCI_EXPORT_DIR)/talos.oci.tar:$(IMAGE_TAG)",skopeo copy "$$SRC" docker://$(REGISTRY)/talos:$(IMAGE_TAG)); \ + $(if $(strip $(OCI_EXPORT_DIR)),mkdir -p "$(OCI_EXPORT_DIR)"; rm -f "$(OCI_EXPORT_DIR)/talos.oci.tar"; skopeo copy "$$SRC" "oci-archive:$(OCI_EXPORT_DIR)/talos.oci.tar:$(IMAGE_TAG)",skopeo copy "$$SRC" docker://$(REGISTRY)/talos:$(IMAGE_TAG)); \🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/talos/Makefile` at line 17, Update the OCI export branch in the Makefile command to remove the existing talos.oci.tar archive with rm -f immediately before invoking skopeo copy, while leaving the registry-copy branch unchanged.hack/common-envs_test.bats (1)
24-24: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winPass
COZYSTACK_VERSION=0in these three tests to keep them hermetic.
hack/common-envs.mkruns a version probe at parse time. WhenCOZYSTACK_VERSIONis empty, it executesgit remote add upstream …andgit fetch upstream --tags(lines 128-132). Tests 4 and 5 already setCOZYSTACK_VERSION=0, ande2e-fork.yamlsets it for the same reason. Tests 1-3 omit it, so a shallow or tagless checkout makes them mutate git remotes and reach the network.♻️ Proposed change
- out=$(make -n -C packages/system/cozystack-controller image OCI_EXPORT_DIR=/tmp/ocitest IMAGE_TAG=pr-1-abc BUILDER=b) + out=$(make -n -C packages/system/cozystack-controller image OCI_EXPORT_DIR=/tmp/ocitest IMAGE_TAG=pr-1-abc COZYSTACK_VERSION=0 BUILDER=b)Apply the same addition to the
capi-providers-cpproviderandtalosinvocations.Also applies to: 37-37, 51-51
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@hack/common-envs_test.bats` at line 24, Set COZYSTACK_VERSION=0 on the three hermetic make invocations in hack/common-envs_test.bats, including the shown system/cozystack-controller command and the capi-providers-cpprovider and talos invocations. Preserve the existing arguments and command structure while preventing parse-time version probing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/api-review-gate-fork.yaml:
- Around line 44-67: Bind fork status publication to the triggering pull
request: in .github/workflows/api-review-gate-fork.yaml lines 44-67, grant
pull-requests: read, query pull requests associated with HEAD_SHA, and publish
nothing unless an open request has head.sha === HEAD_SHA and head.repo.full_name
matching github.event.workflow_run.head_repository.full_name; in
.github/workflows/e2e-fork.yaml lines 143-152, extend the existing
assoc.data.find predicate with those same SHA and repository checks so all
decisions and status targets use the triggering fork’s pull request.
In @.github/workflows/e2e-fork.yaml:
- Around line 304-322: Update the “Derive trusted image allowlist (base tree)”
step so cozystack-operator is always included, matching the installer targets
invoked by the fork build even when image-operator’s prereq output is skipped.
Prefer deriving the name from the same installer/image targets; otherwise
explicitly add cozystack-operator to _allow.txt while preserving the existing
archive-derived entries and validation.
In `@docs/agents/e2e-testing.md`:
- Line 103: Update the section 10 heading to refer to the “E2E Tests” commit
status instead of a check-run, keeping the existing same-repo versus fork
workflow wording unchanged.
---
Nitpick comments:
In `@hack/common-envs_test.bats`:
- Line 24: Set COZYSTACK_VERSION=0 on the three hermetic make invocations in
hack/common-envs_test.bats, including the shown system/cozystack-controller
command and the capi-providers-cpprovider and talos invocations. Preserve the
existing arguments and command structure while preventing parse-time version
probing.
In `@packages/core/talos/Makefile`:
- Line 17: Update the OCI export branch in the Makefile command to remove the
existing talos.oci.tar archive with rm -f immediately before invoking skopeo
copy, while leaving the registry-copy branch unchanged.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 11484404-e518-44c5-b22f-068c661b9230
📒 Files selected for processing (8)
.github/workflows/api-review-gate-fork.yaml.github/workflows/api-review-gate.yaml.github/workflows/e2e-fork.yaml.github/workflows/pull-requests.yamldocs/agents/e2e-testing.mdhack/common-envs.mkhack/common-envs_test.batspackages/core/talos/Makefile
🛑 Comments failed to post (3)
.github/workflows/api-review-gate-fork.yaml (1)
44-67: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Privileged publishers do not bind the status SHA to an open pull request from the triggering fork. Both workflows publish a required merge-gate status using only
workflow_run.head_sha. Neither proves that the SHA is the head of an open pull request whose head repository equalsgithub.event.workflow_run.head_repository.full_name. A fork can point a branch at a commit that another open pull request also uses as its head, so the verdict computed for the fork branch lands on the shared SHA. Commit statuses are deduplicated per context and newest wins, so the fork verdict replaces the verdict a real run reached.
.github/workflows/api-review-gate-fork.yaml#L44-L67: addpull-requests: read, look up the pull requests associated withHEAD_SHA, and post nothing unless an open pull request exists withhead.sha === HEAD_SHAandhead.repo.full_name === github.event.workflow_run.head_repository.full_name..github/workflows/e2e-fork.yaml#L143-L152: extend the existingassoc.data.find(...)predicate withp.head.sha === HEAD_SHAandp.head.repo?.full_name === github.event.workflow_run.head_repository.full_name, so the docs-only decision, the TIA inputs, and the status target all come from the triggering fork's own pull request.📍 Affects 2 files
.github/workflows/api-review-gate-fork.yaml#L44-L67(this comment).github/workflows/e2e-fork.yaml#L143-L152🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/api-review-gate-fork.yaml around lines 44 - 67, Bind fork status publication to the triggering pull request: in .github/workflows/api-review-gate-fork.yaml lines 44-67, grant pull-requests: read, query pull requests associated with HEAD_SHA, and publish nothing unless an open request has head.sha === HEAD_SHA and head.repo.full_name matching github.event.workflow_run.head_repository.full_name; in .github/workflows/e2e-fork.yaml lines 143-152, extend the existing assoc.data.find predicate with those same SHA and repository checks so all decisions and status targets use the triggering fork’s pull request..github/workflows/e2e-fork.yaml (1)
304-322: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash # Inspect the installer image targets and what the derivation would emit. fd -t f 'Makefile' packages/core/installer --exec cat -n {} rg -n 'image-operator|^image:|image-packages' packages/core/installerRepository: cozystack/cozystack
Length of output: 2743
🏁 Script executed:
#!/bin/bash set -euo pipefail # Locate and inspect the workflow derivation and push sections. fd -t f 'e2e-fork.yaml' .github/workflows --exec sh -c ' echo "--- $1 ---" sed -n "300,370p" "$1" | cat -n ' sh {} # Inspect helper functions used in the installer Makefile image target. fd -t f 'common-envs.mk|pre-checks.sh|Dockerfile' --exec sh -c ' echo "--- $1 ---" wc -l "$1" sed -n "1,220p" "$1" | cat -n ' sh {} # Simulate the make -n derivation behavior as shell (read-only from sources). python3 - <<'PY' from pathlib import Path text = Path("packages/core/installer/Makefile").read_text() print("Makefile contains image operator line:", "IMAGE=\"$(REGISTRY)/cozystack-operator" in text) print("make derivation command present:", "make -n" in Path(".github/workflows/e2e-fork.yaml").read_text()) print("operator image name literal:", "cozystack-operator" in text) PYRepository: cozystack/cozystack
Length of output: 50375
🌐 Web query:
GNU make -n rule execution order prerequisities target recipe error handling💡 Result:
In GNU make, the execution flow and error handling follow specific mechanisms: Rule Execution and Prerequisites GNU make processes targets by first identifying the dependency graph [1]. When you request a target, make evaluates its prerequisites to determine if the target is out of date (i.e., if the target does not exist or is older than any of its prerequisites) [1]. It recursively processes these prerequisites, ensuring they are built before the recipe for the main target is executed [2][1]. The order in which rules appear in the makefile does not determine execution order, except that the first non-pattern target in the first makefile becomes the default goal [1]. Error Handling in Recipes When a recipe line (a shell command) returns a non-zero exit status, make treats it as an error [2][3]. By default, make stops executing the current rule and immediately terminates, returning a non-zero status [2][4]. You can modify this behavior using several methods: - Ignore Errors for a Specific Line: Prefix a recipe line with a hyphen
-(e.g., -rm file.o). This causes make to ignore any non-zero exit status for that specific command [2][3]. - Global Ignore: Run make with the -i or --ignore-errors flag to ignore all errors in all recipes [2][4]. Alternatively, you can define the special target.IGNORE in the makefile [2][3]. - Continue Processing: Run make with the -k or --keep-going flag. This instructs make to continue building other independent prerequisites even after a failure occurs, rather than exiting immediately [2][4]. - Cleanup on Failure: To prevent corrupted files from remaining with an updated timestamp after a failed recipe, use the special target.DELETE_ON_ERROR [2][3]. When enabled, make will delete a target if its recipe fails after it has begun modifying the file [2][3]. Expansion Timing Note that make differentiates between variable expansion and prerequisite evaluation [5]. Prerequisites are typically expanded during the first phase (parsing) of the makefile, whereas variables used within recipes are expanded during the second phase (when the recipe is executed) [5].Citations:
- 1: https://web.mit.edu/gnu/doc/html/make_4.html
- 2: https://software.cfht.hawaii.edu/info/make/errors
- 3: https://tack.ch/gnu/make-3.82/make_41.html
- 4: https://www.cs.dartmouth.edu/~cs23/make-documentation/Errors.html
- 5: https://blog.summercat.com/on-gnu-make-prerequisites-expansion.html
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "--- installer Makefile image target ---" sed -n '1,35p' packages/core/installer/Makefile | cat -n echo "--- prereq files/scripts that image: depends on ---" git ls-files packages/core/installer/pre-checks packages/*/hack/pre-checks.sh hack/pre-checks.sh | xargs -r ls -la echo "--- make dry-run if make is available ---" if command -v make >/dev/null 2>&1; then make -n -C packages/core/installer image OCI_EXPORT_DIR=/oci REGISTRY=r COZYSTACK_VERSION=0 | sed -n '1,80p' fiRepository: cozystack/cozystack
Length of output: 1625
🏁 Script executed:
#!/bin/bash set -euo pipefail echo "--- make dry-run output ---" make -n -C packages/core/installer image OCI_EXPORT_DIR=/oci REGISTRY=r COZYSTACK_VERSION=0 | sed -n '1,100p' echo "--- pipeline output for current dry-run string ---" make -n -C packages/core/installer image OCI_EXPORT_DIR=/oci REGISTRY=r COZYSTACK_VERSION=0 | \ grep -oE '(dest=/oci/|oci-archive:/oci/)[a-z0-9._-]+\.oci\.tar' || trueRepository: cozystack/cozystack
Length of output: 227
Add
cozystack-operatorto the image allowlist.The derivation only scans
make -n imageoutput fordest=.../.oci.tar, but the installerimagetarget builds the operator first withimage-tags(cozystack-operator, ...)and writes that image name topr-oci-installer. A failed or skippedpre-checksprereq stops the recipe forimage-operator, so the operator archive can be excluded while the push still createsr/cozystack-operator@.... Derive from the same targets the fork build invokes, or includecozystack-operatorreliably.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/e2e-fork.yaml around lines 304 - 322, Update the “Derive trusted image allowlist (base tree)” step so cozystack-operator is always included, matching the installer targets invoked by the fork build even when image-operator’s prereq output is skipped. Prefer deriving the name from the same installer/image targets; otherwise explicitly add cozystack-operator to _allow.txt while preserving the existing archive-derived entries and validation.docs/agents/e2e-testing.md (1)
103-103: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the section heading: the gate is a commit status.
The heading says
check-run, but the section states the opposite and explains why a check run is wrong. The heading is the first thing a reader sees, so it inverts the invariant.📝 Proposed change
-### 10. The E2E workflow split: same-repo vs fork, and the "E2E Tests" check-run +### 10. The E2E workflow split: same-repo vs fork, and the "E2E Tests" commit status📝 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.### 10. The E2E workflow split: same-repo vs fork, and the "E2E Tests" commit status🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/agents/e2e-testing.md` at line 103, Update the section 10 heading to refer to the “E2E Tests” commit status instead of a check-run, keeping the existing same-repo versus fork workflow wording unchanged.
|
Thanks — the core of this was right and it changed the PR substantially. Two parts of the stated mechanism didn't survive checking, though, and since you explicitly asked not to take your word for it, here is what I measured. All of it is read-only against real commits in this repo, so no scratch PR was needed. The load-bearing assumption does not hold — check runs are not deduplicated per name. You asked to verify that "for one check-context name on one commit, the most-recent check run wins." It does not. Each workflow run gets its own check suite, and And the aggregate is worst-of, not latest-wins. Commit But your conclusion was right, for a different reason, and it's the reason the PR got rebuilt. The real regression is that a What the PR does now. Rather than tuning the Two consequences worth flagging since they weren't in the original PR either. A fork PR cannot publish this status from any trigger — the read-only-token restriction covers Your three suggested directions all pointed at this; option 2 in particular is close to what the reporting job does, just expressed as "post nothing" instead of "re-post the previous conclusion", which needs no lookup and cannot race. Thanks for pushing back on the "YAML parses clean" testing section — that was the actual weak point. |
The problem
The API Review Gate's merge context was its job name. That inherits two GitHub behaviours which together make it both unenforceable and self-poisoning, and the second one is not widely known — it is why this PR replaced its own first approach.
A required job that skips counts as PASSED by branch protection. Any path that skips the evaluating job hands out a free pass. This is the same hole that let fork PRs merge with e2e never having run (#3257), which #3262 fixes for
E2E Tests.Check runs are not deduplicated per name. Every workflow run gets its own check suite, so
GET /commits/{sha}/check-runs?filter=latestreturns a byte-identical set tofilter=all—filter=latestonly collapses re-runs within a suite. Two runs on one head SHA therefore leave two verdicts standing for one context name, and the aggregate is worst-of, so a single cancelled run poisons the context until the next push. Measured on this repo:41d1153ccarries four check runs namedRequire API owner review for sizeable API changes(onecancelled, threesuccess);0c14d6cfcarriesE2E Testsascancelledat 12:51:22 andskippedat 12:51:23, with every other context green and a rollup of FAILURE — the laterskippeddid not supersede the earliercancelled.Both problems are live today, not hypothetical.
41d1153cand4b1966c5each show apull_requestrun cancelled by apull_request_reviewrun seconds later: workflow-level concurrency kills the anchor before any job-levelif:is evaluated, so a review bot commenting during the ~30s evaluation leaves a stalecancelledas that SHA's only api-gate verdict.The fix
Publish the gate as a commit status. The evaluating job is renamed off the gate context, and
API Review Gateis published by a dedicated reporting job —api-gate-reportfor same-repo PRs, and a privilegedworkflow_run(api-review-gate-fork.yaml) for forks. Commit statuses are deduplicated per context with the newest winning (verified:0c14d6cfhas sixCodeRabbitstatuses; the combined status returns exactly one), which buys the invariant the whole design rests on: skipped and cancelled runs post nothing, so declining to post can only preserve a verdict some real run reached — never invent one.Stop re-evaluating on review-bot comments. Plain
commentedreviews — all the bots ever post — cannot move a verdict that counts onlyAPPROVEDreviews, so they skip the evaluation instead of rebuilding the detector each time. This is now safe on its own terms: the reporting job skips in lockstep and writes nothing, leaving the previous status untouched. It no longer depends on skip-to-green, nor on any assumption about how duplicate check runs are resolved.Split the concurrency group so a run that computes no verdict cannot cancel one that does. Bot-comment runs get their own key; pushes and actionable reviews share
evaland legitimately supersede each other. This also fixes the pre-existing cancellation defect above, which no amount ofif:tuning addresses.Fork PRs need the privileged half. A fork's
GITHUB_TOKENis read-only on both thepull_requestand thepull_request_reviewtrigger — the restriction is notpull_request-specific — so no run of the gate workflow can post a status for a fork. The companion runs from the default branch after the gate run completes. It checks nothing out, builds nothing, and executes no fork-authored code: its only inputs are the triggering run's conclusion and head SHA, both base-repo payload data, with the SHA regex-validated before it reaches the API. That is why this is aworkflow_runsplit and not apull_request_targetrun.Testing
actionlintclean on both workflows;yaml.safe_loadparses; foldedconcurrencyandif:expressions verified to render as intended single-line strings.zizmor(pre-commit,--offline) passes — but note itsdangerous-triggersandtemplate-injectionaudits are disabled repo-wide in.github/zizmor.yml, so that pass is not meaningful evidence for a new privileged workflow. Audited manually against both instead: no checkout, no fork code executed, every payload value routed throughenv:rather than shell interpolation, andstatuses: writeconfined to the reporting jobs (the evaluating job keepscontents: read+pull-requests: read).Not yet exercised live, and the reason this is a draft: the fork companion cannot run until it is on the default branch (
workflow_runonly runs from there), the same constraint that keeps #3262 in draft. The same-repo path is exercisable normally. Thecommented-review skip only truly exercises when a bot comments on a live PR.Repo-admin prerequisite
For the gate to block a merge, branch protection must require the
API Review Gatestatus context — not a job name, and no app-id pin. This is a repository setting outside this diff. Until it is set the status is advisory, exactly as the job name was, so this PR is not a change in enforcement — only in whether enforcement is possible and correct once switched on.