Skip to content

ci: a run a newer push has overtaken skips itself - #1778

Merged
mobeenabdullah merged 9 commits into
mainfrom
ci/a-superseded-run-skips-itself
Sep 11, 2026
Merged

mobeenabdullah merged 9 commits into
mainfrom
ci/a-superseded-run-skips-itself

Conversation

@mobeenabdullah

@mobeenabdullah mobeenabdullah commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

Replaces #1766, closed after review found four more ways a cancelled run could lie.

The problem it solves

The CI and Integration workflows key concurrency on main by commit, so every merge keeps a verdict of its own and nothing is ever cancelled (#688, deliberately). At the current merge rate that queue could not drain: Integration is 3 dialects × 20–35 min, and 50 runs sat queued with nothing on main able to report for over half an hour, including the commit that fixed the failure everyone was waiting on.

Why not cancel

#1766 tried it. cancelled then had to mean superseded, or stopped by hand, or timed out, and every reader of the check needed rules to tell those apart. Two rules were added; review found four more; the next round would have found a seventh. That is an overloaded signal, not an incomplete implementation.

What this does instead

Nothing is cancelled. A composite action (.github/actions/superseded-on-main) asks at the start of a run on main whether a newer push already has a run of the same workflow. If so, the run's jobs are skipped.

A skipped job is already how this repository says "this commit cannot affect me", it already passes the merge gate, and here it is literally true: the newer run's verdict includes this commit. cancelled keeps meaning what it says. Queued runs start, ask, and stop within seconds; only the newest tests. Per-commit groups stay exactly as #688 left them.

The hole this opens, closed in the same change

CI's changes job diffs a push against event.before. Once a code push can skip itself, a README-only push landing after it would diff against that push alone, read as inert, and leave the code untested. That hole doesn't exist today only because nothing is ever superseded.

So the action also names the last commit whose substantive job actually ran, found by walking completed runs newest-first, and changes diffs against that on main. Integration has no inert path and needs only the first answer.

Also

  • ci-gate.mjs learns supersession as a second acceptable reason for a skip, kept separate from inert so the gate never says a superseded run "touched only inert paths." Tests for both.
  • The ceiling guard held every job in integration.yml to a floor sized for a suite leg. It now holds the jobs that run the budget wrapper; the pre-job asks a question and an hour's ceiling would let a hung request keep a runner for an hour. Verified the guard still fails when a real leg's ceiling drops.
  • actions: read is granted to the asking job alone.
  • verify-merge.mjs needs no change beyond two comments: skipped already passes, and the required names are now literal. The inference added in ci: a newer main push supersedes the run in flight #1766 never lands.

The skipped matrix, measured

A job-level if is evaluated before strategy.matrix is applied (GitHub's workflow-syntax reference says so verbatim). A superseded run would therefore report the postgres/mysql matrix as ONE skipped check, and verify-merge would call both required checks absent on every superseded merge commit. The sqlite leg is not a matrix and keeps its name.

The first fix named the matrix job for what happened via a needs-based expression. A throwaway probe workflow then measured what GitHub actually does with a skipped matrix job: its name expression is not evaluated at all. The check was named with the raw source text, JobLevel (${{ matrix.dialect }}), and the needs ternary appeared verbatim. So that design is gone, and with it the verify-merge stand-in it needed.

What replaced it is the plain answer: one job per dialect with a literal name. A literal name survives a skip, so each leg reports as Integration (postgres) / (mysql) / (sqlite), skipped, which is what the gate already accepts; verify-merge needs no change beyond a comment saying why the names are literal. The split also ends both databases booting for both legs, which the matrix had forced ("a services: entry cannot be chosen per matrix value"): each job declares only the one it connects to. The setup steps the legs had as copies (pnpm, Node, job-scoped Turbo cache, install) now live once in .github/actions/integration-setup.

What was wrong in the first revision, and how each was found

  • gh api --jq --arg does not exist (accepts 1 arg(s), received 4). The last-tested walk failed silently and fell back to event.before, the hole it exists to close. Reproduced against a real run id, fixed by binding the name in jq over the raw response, and the whole script was then run locally against real main run ids in all four branches (superseded, walk, Integration's parenthesised job name, pull request).
  • Only success and failure count as a tested base now. A run cancelled or timed out at setup judged nothing.
  • The run listing read one page of 100 instead of paginating a workflow with 1,630 runs on main.

Also fixed on review

  • An unknown tested base (history read failed, or no verdict on the newest 100 runs) fell back to event.before, the hole last-tested-sha exists to close. It now runs everything.
  • The comment-convention job hung off nothing and kept a runner for up to ten minutes on every overtaken run while the gate waited. It now needs changes and skips on supersession alone; still ungated on inert, for the reason its comment gives.
  • A newer run counts as covering only while it can still reach a verdict (queued, in progress, success, failure). One already cancelled or timed out covers nothing. A covering run cancelled after the fact is recovered by the next push, whose diff base walks past cancelled runs.
  • A YAML parse test now covers every workflow and local action manifest; one revision here carried an unquoted : inside an expression and would have loaded as nothing.

What to watch on the first real superseded push to main

That the three Integration checks appear under their own names as skipped, CI gate and the comment scan skip, and node scripts/verify-merge.mjs <pr> on that merge commit passes. This PR's own Integration run exercises the split jobs and the shared setup action on the pull-request trigger.

CI-only, no changeset. scripts 1,301 · lint 0 · comments 0.

The CI and Integration workflows key their concurrency on `main` by commit,
so every merge keeps a verdict of its own and nothing is ever cancelled. #688
chose that deliberately, because a cancelled required check looked the same
as a failed one. It could not survive the merge rate: each Integration run is
three dialects for half an hour, and fifty runs sat queued with nothing on
`main` able to report for over half an hour.

Cancelling the older run was tried in #1766 and closed. `cancelled` then had
to mean superseded, or stopped by hand, or timed out, and every reader of the
check needed rules to tell those apart. Review found four more ways it could
lie after two were already guarded, and the next round would have found a
seventh. That is an overloaded signal, not an incomplete implementation.

So nothing is cancelled. A composite action asks, at the START of a run on
`main`, whether a newer push already has a run of the same workflow; if so
the run's jobs are skipped. A skipped job is already how this repository says
"this commit cannot affect me", it already passes the merge gate, and here it
is literally true: the newer run's verdict includes this commit. `cancelled`
keeps meaning what it says. Queued runs start, ask and stop within seconds,
and only the newest tests, so the queue stays short with per-commit groups
kept exactly as #688 left them.

One hole this opens is closed in the same change. CI's `changes` job diffs a
push against `event.before`, the previous commit. Once a code push can skip
itself, a README-only push landing after it would diff against that push
alone, read as inert, and leave the code untested. The action therefore also
names the last commit whose substantive job actually ran, found by walking
completed runs newest-first, and `changes` diffs against that on `main`.
Integration has no inert path and needs only the first answer.

The gate learns supersession as a second reason a skip is acceptable, kept
separate from inert so a superseded run is never described as having touched
only inert paths. The ceiling guard, which held every job in integration.yml
to a floor sized for a suite leg, now holds the jobs that run the budget
wrapper; the pre-job asks a question and holding it to an hour's ceiling would
let a hung request keep a runner for an hour. Verified the guard still fails
when a real leg's ceiling drops.

Permissions: `actions: read` is granted to the asking job alone, so no job
that builds or tests holds it.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@coderabbitai

coderabbitai Bot commented Sep 11, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 2 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: c52e84ab-19a2-4b5b-9f89-1abadfac1fb3

📥 Commits

Reviewing files that changed from the base of the PR and between 4a34f1e and a4cef1d.

📒 Files selected for processing (11)
  • .claude/rules/verifying-merged-work.md
  • .github/actions/integration-setup/action.yml
  • .github/actions/superseded-on-main/action.yml
  • .github/workflows/ci.yml
  • .github/workflows/integration.yml
  • scripts/ci-gate.mjs
  • scripts/ci-gate.test.mjs
  • scripts/github-yaml-parses.test.mjs
  • scripts/integration-legs-fail-on-overrun.test.mjs
  • scripts/verify-merge.mjs
  • scripts/workflow-run-blocks.mjs

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.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-11T12:40:46.114264Z a4cef1d Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 79b0b39a44

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/actions/superseded-on-main/action.yml Outdated
Comment thread .github/actions/superseded-on-main/action.yml Outdated
Comment thread .github/workflows/integration.yml Outdated
Comment thread .github/actions/superseded-on-main/action.yml Outdated
…sted

gh api --jq takes one filter and no --arg, so the jobs lookup exited before
reading anything and every main push fell back to event.before. The name is
now bound by jq over the raw response, only success and failure count as a
tested base, and the run listing reads one page instead of the workflow's
whole history.
…-merge read it

A job-level if is applied before the matrix expands, so a superseded run
reported one skipped check with an empty dialect and no postgres or mysql
check at all, and verify-merge called both required checks absent on every
superseded merge commit. The job now names itself
'Integration (superseded: postgres, mysql)' in that case; verify-merge
accepts that name, skipped, as the two legs' stand-in and reports each leg
it answered for. A test pins the string to the workflow's own text.
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Whole-Repository Code Hygiene Summary

Full dead-code, duplication, and complexity report for the PR branch as it stands now. Playground is excluded. Quality gate enforcement on introduced issues is performed by the Changed files job.

🌿 Fallow

Warning

Review needed

⚠️ 73 code issues · ⚠️ 694 clone groups · ⚠️ 1044 health findings

See inline review comments for per-finding details.

Code issues (73)
Category Count
Unused files 2
Unused exports 5
Unused dependencies 19
Unused devDependencies 6
Unresolved imports 2
Unlisted dependencies 1
Circular dependencies 38
Duplication (694 groups · 29077 lines · 4.1%)
Locations Lines Tokens
schemas/_dialect-bundles/mysql.relations.ts:40-134
schemas/_dialect-bundles/postgres.relations.ts:40-134
schemas/_dialect-bundles/sqlite.relations.ts:40-134
95 593
cli/commands/db-sync-demote.ts:70-75
cli/commands/db-sync-promote.ts:38-43
cli/commands/dev-build.ts:100-105
cli/commands/dev-build.ts:179-184
cli/commands/dev-build.ts:299-304
cli/commands/dev-build.ts:411-416
cli/commands/dev-build.ts:552-557
cli/commands/dev-server.ts:575-580
cli/commands/dev-server.ts:840-845
cli/commands/dev-server.ts:1143-1148
cli/commands/migrate-field-groups.ts:110-115
6 70
entries/EntryList/EntryTableSkeleton.tsx:74-98
collection/components/CollectionTableSkeleton.tsx:94-118
field-group/components/FieldGroupTableSkeleton.tsx:90-114
plugins/components/PluginsTableSkeleton.tsx:86-110
singles/components/SinglesTableSkeleton.tsx:77-101
src/components/table-skeleton.tsx:100-124
25 89
collections/config/validate-config.ts:380-433
field-groups/config/validate-field-group.ts:185-238
singles/config/validate-single.ts:190-243
54 152
dispatcher/handlers/collection-dispatcher.ts:922-964
field-groups/services/field-group-table-provisioning.ts:186-236
singles/services/reconcile-single-companion.ts:110-160
51 149

… and 689 more groups.

Across 423 files.

Complexity (1044 functions above threshold)
File Function Severity Cyclomatic Cognitive CRAP Lines
singles/services/single-mutation-service.ts:981 <arrow> critical 251 ! 324 ! 13859.2 ! 1625
collections/services/collection-mutation-service.ts:6209 <arrow> critical 174 ! 177 ! 6713.6 ! 1301
src/init/reload-config.ts:1319 applyReload critical 144 ! 228 ! 4623 ! 1433
shared/lib/entry-validation.ts:223 validateFieldValue critical 109 ! 157 ! 2675.3 ! 432
blocks-engine/src/measure-bytes.ts:646 surveyDocument critical 102 ! 250 ! 137.1 ! 658

5001 files, 76567 functions analyzed (thresholds: cyclomatic > 20, cognitive > 15, CRAP >= 30)

Codebase health

Metric Value
Maintainability 91.7 / 100
Avg complexity 1.8

Tip

Run fallow fix --dry-run to preview auto-fixes.
Add /** @public */ above exports to preserve them.

@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 80949995c2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/actions/superseded-on-main/action.yml
Comment thread .github/workflows/ci.yml
@github-actions github-actions Bot added the type: docs Documentation only label Sep 11, 2026
… scan when superseded

An unknown tested base fell back to event.before, which is the hole the
last-tested-sha output exists to close; an empty base now runs the full
workflow. The comment-convention job hung off nothing and kept a runner
for up to ten minutes on every overtaken run while the gate waited; it
now needs the changes job and skips on supersession alone.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b30d6e831

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/integration.yml Outdated
…action

A matrix job skipped at the job level reports one check named with its
unevaluated name expression, needs and all; measured on a probe run. So
the postgres and mysql legs are now two jobs with literal names that
survive a skip, each booting only its own database, and the setup steps
the three legs shared as copies live in .github/actions/integration-setup.
verify-merge needs no stand-in: the required names are present, skipped.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

A job name expression carrying an unquoted ': ' reached a pushed branch
and would have loaded the workflow as nothing on main; no local gate reads
.yml. Every file under .github/workflows and each action.yml is now parsed
with js-yaml, with the offending shape pinned as the negative case.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2c284e84ad

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/actions/superseded-on-main/action.yml Outdated
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

A newer run already cancelled or timed out tested nothing, so a run it
would have superseded now does its own work. A covering run cancelled
later is recovered by the next push, whose diff base is the last commit
that reached a verdict.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b4ecc246cc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/integration.yml Outdated
GitHub skips every dependent of a failed job, skipped passes the merge
gate, and the integration workflow has no aggregate gate of its own, so
a failed supersession question would have read as three acceptable
required checks with nothing run. The legs now run unless the workflow
itself was cancelled.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: a4cef1d9ba

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@mobeenabdullah
mobeenabdullah merged commit 4e7921e into main Sep 11, 2026
13 checks passed
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

One-time backlog cancellation, by the founder's decision after this merged.

The runs queued on main before this landed were created from commits whose workflow files have no skip, so they would have run in full ahead of the first new-style run. All 35 CI and Integration runs on main whose head commit is an ancestor of this merge commit (4e7921e35) were cancelled at 13:10Z; every one of those trees is contained in this commit's, whose run now goes first. The queued Release run was left alone: that workflow's own concurrency group handles it. Nothing on any pull-request branch was touched.

Those 35 commits show cancelled on main. That is the label #688 avoided, and here it is the truth: superseded before the mechanism existed.

@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

Live verification, first superseded push after the merge: Integration run 34602571259 for 9e59229 (#1788's merge commit) asked, found ebf8f78 already had a newer run, and skipped. The action's notice: superseded: ebf8f78 already has a newer run of integration.yml on main; skipping. Check-runs on that commit: Integration (postgres), (mysql), (sqlite), each completed/skipped under its own name, which is what verify-merge requires and accepts. The run held a runner for the ask alone.

@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

Live verification, the other half: Integration run 34603323315 for ebf8f78 (the newest push at the time) asked, found nothing newer, ran all three legs in full, and every dialect concluded success. Three superseded pushes before it (9e59229, cd95546, 985a438) skipped under their own names. main's Integration is green under the new workflow, the observation the queue had made impossible since #1735.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: docs Documentation only

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant