Skip to content

ci(root): stop a main run from cancelling the commit before it - #688

Merged
mobeenabdullah merged 2 commits into
mainfrom
ci/main-runs-are-not-superseded
Aug 11, 2026
Merged

mobeenabdullah merged 2 commits into
mainfrom
ci/main-runs-are-not-superseded

Conversation

@mobeenabdullah

Copy link
Copy Markdown
Collaborator

Closes the mechanism behind task 205. A required check that reports cancelled is indistinguishable from one that failed, and the habit that teaches — merge past red, the red is never mine — is the actual cost.

The measurement

Last eight origin/main commits, newest run per name:

check result
Browser tests 2 success · 5 cancelled · 1 in progress · 0 failures
Integration (postgres / mysql / sqlite) 3 success · 5 cancelled

The task file recorded 0 successes in 6 with two outright failures. Those failures are gone — #669 and #663 landed in between — so what remains is entirely cancellation.

The cancellations fall on exactly the same five commits in both workflows: e1d573e23, 3e875bcc2, 9368cf6b2, 330ce9994, 249649eb9. Two independent required checks cancelling together on the same commits is much stronger evidence of one shared cause than either measurement alone.

The mechanism

Read from the workflow rather than inferred:

concurrency:
  group: ci-${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

On main, github.ref is the same string for every push. So each merge supersedes and cancels the previous commit's in-flight run, and with several lanes merging, most main commits never reach a verdict.

The change

cancel-in-progress: ${{ github.event_name == 'pull_request' }}, in ci.yml and integration.yml.

Cancellation is kept where it is correct: on a pull request a new push makes the previous run's verdict irrelevant, so cancelling saves the minutes and loses nothing. It is dropped on main, where each commit is a distinct artifact someone will later ask "was that green?" about.

Scope is wider than the task's title

integration.yml carries the identical shape and also runs on main, so this covers three more required checks — the task named only Browser tests, and fixing just that would have left the same defect in place for the integration legs.

preview.yml has the same setting and is deliberately unchanged: it triggers on pull_request only, where superseding is correct.

What this PR does NOT establish

Task 205 sets the bar at five consecutive green main commits with no re-runs, and no PR can demonstrate that — it is only observable after this lands and five more merges happen. Three of the six original data points were cancellations, which are inherently intermittent, so a single green after merge would prove nothing either.

The task file stays open with the bar written down and the re-measurement recorded. I would rather say that than let a merged PR imply the bar was met.

Explicitly not done

Making the checks non-required. That converts "browser coverage is broken" into "browser coverage is optional", and the second state is permanent because nothing forces it back.

Notes

  • No changeset: CI-only.
  • YAML parses; the expression is the standard GitHub Actions form and evaluates per-event.

On `main` `github.ref` is one string for every push, so each merge superseded
and cancelled the previous commit's in-flight run. Measured over the last eight
commits: `Browser tests` 2 success / 5 cancelled, and the three `Integration`
legs 3 success / 5 cancelled — on exactly the same five commits, which is what
points at one shared cause rather than two flaky jobs.

A required check reporting `cancelled` is indistinguishable from one that
failed, so the habit it teaches is to merge past red. That is the cost worth
fixing, more than the missing coverage.

Cancellation stays for pull requests, where a new push makes the previous
verdict irrelevant and the minutes are worth saving. `preview.yml` carries the
same setting and is left alone: it triggers on pull requests only.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex please review this PR

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@mobeenabdullah, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 58 minutes

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

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e2e273bc-f02c-4fff-afdd-d22af857b427

📥 Commits

Reviewing files that changed from the base of the PR and between 175ed53 and 88dfa98.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • .github/workflows/integration.yml

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 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: bc6110ad2e

ℹ️ 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/ci.yml Outdated
@pkg-pr-new

pkg-pr-new Bot commented Aug 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@nextlyhq/adapter-drizzle

npm i https://pkg.pr.new/@nextlyhq/adapter-drizzle@88dfa98

@nextlyhq/adapter-mysql

npm i https://pkg.pr.new/@nextlyhq/adapter-mysql@88dfa98

@nextlyhq/adapter-postgres

npm i https://pkg.pr.new/@nextlyhq/adapter-postgres@88dfa98

@nextlyhq/adapter-sqlite

npm i https://pkg.pr.new/@nextlyhq/adapter-sqlite@88dfa98

@nextlyhq/admin

npm i https://pkg.pr.new/@nextlyhq/admin@88dfa98

@nextlyhq/admin-css

npm i https://pkg.pr.new/@nextlyhq/admin-css@88dfa98

@nextlyhq/blocks-engine

npm i https://pkg.pr.new/@nextlyhq/blocks-engine@88dfa98

@nextlyhq/blocks-react

npm i https://pkg.pr.new/@nextlyhq/blocks-react@88dfa98

@nextlyhq/builder

npm i https://pkg.pr.new/@nextlyhq/builder@88dfa98

create-nextly-app

npm i https://pkg.pr.new/create-nextly-app@88dfa98

nextly

npm i https://pkg.pr.new/nextly@88dfa98

@nextlyhq/plugin-form-builder

npm i https://pkg.pr.new/@nextlyhq/plugin-form-builder@88dfa98

@nextlyhq/plugin-page-builder

npm i https://pkg.pr.new/@nextlyhq/plugin-page-builder@88dfa98

@nextlyhq/plugin-sdk

npm i https://pkg.pr.new/@nextlyhq/plugin-sdk@88dfa98

@nextlyhq/plugin-seo

npm i https://pkg.pr.new/@nextlyhq/plugin-seo@88dfa98

@nextlyhq/storage-s3

npm i https://pkg.pr.new/@nextlyhq/storage-s3@88dfa98

@nextlyhq/storage-uploadthing

npm i https://pkg.pr.new/@nextlyhq/storage-uploadthing@88dfa98

@nextlyhq/storage-vercel-blob

npm i https://pkg.pr.new/@nextlyhq/storage-vercel-blob@88dfa98

@nextlyhq/ui

npm i https://pkg.pr.new/@nextlyhq/ui@88dfa98

commit: 88dfa98

Switching cancellation off preserved the RUNNING job and not the queued one. A
concurrency group holds at most one running and one pending run, so three
merges in quick succession displace the middle commit while it is still
queued — the same missing verdict, reached a different way.

Keyed by `github.sha` on push there is nothing to displace. Pull requests keep
the shared per-ref group, where superseding is what you want.
@mobeenabdullah

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🚀

Reviewed commit: 88dfa98865

ℹ️ 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 48c04f8 into main Aug 11, 2026
17 checks passed
mobeenabdullah added a commit that referenced this pull request Sep 11, 2026
* ci: a run a newer push has overtaken skips itself

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.

* ci: bind the substantive job name in jq and count only verdicts as tested

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.

* ci: name the skipped integration matrix for what it is and let verify-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.

* ci: run everything when no tested base is known, and skip the comment 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.

* ci: one literal-named integration job per dialect, sharing one setup 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.

* test(scripts): every workflow and local action manifest parses as YAML

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.

* ci: a newer run covers this one only while it can still reach a verdict

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.

* ci: an integration leg falls open when the supersession job fails

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.
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