Skip to content

fix(test-integrity): keep a verdict when an event cannot change it - #98

Merged
caiopizzol merged 5 commits into
mainfrom
fix/test-integrity-keep-result
Sep 28, 2026
Merged

caiopizzol merged 5 commits into
mainfrom
fix/test-integrity-keep-result

Conversation

@caiopizzol

@caiopizzol caiopizzol commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Problem

Since #96, the tests-changed-ok label only accepts a change on the run that adds it. Any later event runs the check again without the label, so the approval is lost to events that cannot change the verdict:

  • a title or body edit;
  • any other label, such as a release bot's.

The required check then turns red on an approved pull request.

Change

  • Record. Every verdict writes a record on its own job: a notice annotation test-integrity-result with pr=… base=… head=… version=… verdict=pass|fail. version is the action's commit (github.action_ref).
  • Reuse. On an edited event that keeps the base, or a labeled / unlabeled event for another label, the action looks at the newest earlier run of this workflow (workflow_id), started by pull_request_target, on the head commit. Newest means its latest attempt started last. It reuses that run's verdict only when all of these hold:
    • exactly one job has this job's name (read from job.check_run_id);
    • that job completed with success or failure;
    • it has exactly one well-formed record;
    • the record matches this pull request, base, head, and detector version, and agrees with the job's conclusion.
  • Everything else is evaluated again, including:
    • pushes, reopens, and base changes;
    • the exception label itself;
    • a newest run that was cancelled or has no clear record (no fallback to an older verdict, which the label may no longer back);
    • a detector pinned by branch or tag;
    • a workflow without actions: read and checks: read;
    • any list of runs, jobs, or annotations that was not read in full.

The decision is a pure module (prior.ts) with its own tests; the action only gathers the inputs.

Why the trust boundary holds

  • pull_request_target runs the base branch's copy of the workflow, so a pull request cannot add jobs to it.
  • A workflow the pull request adds has another workflow_id, and this file run by pull_request has another event.
  • Another job in the same workflow is ignored, because only the job named like this one counts.
  • A rerun replays the original event, so the label is honored only on a run's first attempt. Otherwise, rerunning an old approval after the label was removed would pass again and become the newest verdict.
  • Callers must give this action a job of its own, uniquely named, that runs no pull request code. The action's description says so.

Verification

  • bun run check passes. Package: 84 tests.
  • Each guard has a control. Removing the job-name filter, the exactly-one rule, the attempt-start ordering, the unreadable-records guard, the workflow and event filter, the self-exclusion, the paging check, the pull request match, or the commit-SHA rule fails a named test.
  • The gather step was run locally against a real lab pull request: it read the right workflow and job name and all annotations, and excluded the forged workflow.
  • Real events on a disposable public repository, with the check required and the pull request deleting a test.

On the final commit, the calling workflow has a second job that prints a forged passing record for the exact pull request, base, head, and version on every run:

Event Result
Opened failure
Unrelated label (forged pass in the same run) failure reused, blocked
Labeled tests-changed-ok success
Unrelated unlabel success reused, clean
Title edited success reused, clean
tests-changed-ok removed failure
The approval run rerun (it replays the labeled event) failure: only a first attempt accepts
Unrelated label failure reused from that rerun, blocked

On an earlier commit, a pull request added a pull_request workflow whose Test integrity job forged a passing record. An unrelated label still reused the real failure. Also on that commit, tests-changed-ok was removed and its run cancelled; a later unrelated unlabel evaluated fresh and failed, instead of reusing the old pass.

Callers need actions: read and checks: read in the workflow's permissions to keep verdicts. Without them, behavior is the same as today.

🤖 Generated with Claude Code

caiopizzol and others added 2 commits September 28, 2026 10:55
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…mmit

A branch or tag pin keeps the same action_ref while its code changes, so a
verdict recorded under it could outlive the detector that produced it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@caiopizzol
caiopizzol enabled auto-merge (squash) September 28, 2026 14:07

@cubic-dev-ai cubic-dev-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.

All reported issues were addressed across 4 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread .github/actions/test-integrity/action.yml Outdated
Comment thread packages/test-integrity/src/prior.ts Outdated
Read records from this workflow's own pull_request_target runs, not any check
named Test integrity, which a workflow in the pull request could forge. Use the
newest earlier run only: when it has no clear verdict, evaluate again rather
than fall back to an older pass the label may no longer support. Bind records
to the pull request number, and distrust a run list cut short by paging.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@caiopizzol
caiopizzol disabled auto-merge September 28, 2026 14:40
caiopizzol and others added 2 commits September 28, 2026 12:05
… attempt

Another job in the caller's workflow may run pull request code and print a
record, so take records only from the one job whose check run name matches
this job's. Order runs by when their latest attempt started, so a rerun of an
older run counts as newest, and trust jobs and annotations only when every
page was read.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A rerun replays the original labeled event even after the label is removed,
so it would approve the change again and become the newest verdict to reuse.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-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.

All reported issues were addressed across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread .github/actions/test-integrity/action.yml
@caiopizzol
caiopizzol enabled auto-merge (squash) September 28, 2026 15:19
@caiopizzol
caiopizzol merged commit a0bcd3d into main Sep 28, 2026
2 checks passed
@caiopizzol
caiopizzol deleted the fix/test-integrity-keep-result branch September 28, 2026 15:20
caiopizzol added a commit to caiopizzol/pagesight that referenced this pull request Sep 28, 2026
…39)

Re-pin the test-integrity action to caiopizzol/skills#98 and grant actions: read and checks: read, so an edit or another label reuses the verdict already on the commit instead of running without the label.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
caiopizzol added a commit to caiopizzol/moor that referenced this pull request Sep 28, 2026
…212)

Re-pin the test-integrity action to caiopizzol/skills#98 and grant actions: read and checks: read, so an edit or another label reuses the verdict already on the commit instead of running without the label.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
caiopizzol added a commit to caiopizzol/namecheap that referenced this pull request Sep 28, 2026
…14)

Re-pin the test-integrity action to caiopizzol/skills#98 and grant actions: read and checks: read, so an edit or another label reuses the verdict already on the commit instead of running without the label.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
caiopizzol added a commit to caiopizzol/linkkeeper that referenced this pull request Sep 28, 2026
…21)

Re-pin the test-integrity action to caiopizzol/skills#98 and grant actions: read and checks: read, so an edit or another label reuses the verdict already on the commit instead of running without the label.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
caiopizzol added a commit to caiopizzol/kicktires that referenced this pull request Sep 28, 2026
…58)

Re-pin the test-integrity action to caiopizzol/skills#98 and grant actions: read and checks: read, so an edit or another label reuses the verdict already on the commit instead of running without the label.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.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.

1 participant