Skip to content

ci: stop main merges evicting each other's CI runs, and stop triage citing a baseline that never ran the job - #2209

Merged
BigSimmo merged 5 commits into
mainfrom
claude/pr-review-comments-yegd3q
Aug 21, 2026
Merged

ci: stop main merges evicting each other's CI runs, and stop triage citing a baseline that never ran the job#2209
BigSimmo merged 5 commits into
mainfrom
claude/pr-review-comments-yegd3q

Conversation

@BigSimmo

Copy link
Copy Markdown
Owner

Summary

Three independent CI-signal defects found while reviewing PRs #2199 and #2201. Each is its own commit, and each is separately revertible.

  • main merges were evicting each other's CI runs (5f9b4a5). cancel-in-progress: ${{ github.event_name != 'push' }} stops a later merge superseding an in-flight main run, and it does that. It does not stop the other way a run dies: GitHub keeps at most one pending run per concurrency group, so when merges land faster than a run completes, each newly queued main run cancels the one already waiting. No supersession is involved, so the exemption never sees it. Base-branch pushes now key concurrency on github.run_id, exactly as workflow_dispatch and schedule already do. cancel-in-progress stays as the statement of intent and as the fallback if the group key ever changes.

  • The CI triage bot cited a baseline that never ran the failing job (0916c9b). CI is path-scoped, so a docs-only push to main reports success with Lighthouse, Production UI and Build all skipped. The comment still printed Compared with main CI run #N (success) and labelled the failure "needs investigation", which reads as "main is green for this job" when main had never measured it. The baseline's jobs are now read whatever its conclusion; a failing job the baseline did not execute is classified unbaselined and the comparison sentence names it. classifyFailedJobs takes the executed-name list as an optional fourth argument — omitted means "could not establish", so the workflow's catch path degrades exactly as before.

  • tests/guard-push.test.ts could not run its own subject without gh on PATH (2e535bd). inFlightCiGuard probes for the gh binary and fails open before either injected fetcher is consulted, so the message-formatting case was green in CI and red in any bare container, for no product reason. ghAvailable now joins prViewer/runFetcher as an injectable defaulting to the real probe, and a new case pins the fail-open branch itself — asserting neither fetcher is consulted when gh is absent, which nothing covered before.

These are not hypothetical. The concurrency hole let a mobile-/ CLS regression land on main unmeasured on 2026-08-20; it then surfaced on unrelated PR #2199 as that PR's failure, and the triage bot pointed at run #12662 — a docs-only merge that skipped Lighthouse entirely — as the green baseline. Two of the three defects combined to misattribute one regression.

Verification

  • npm run verify:pr-local
PR-local verification summary:
- completed: check:runtime, check:installed-lock-parity, format:changed, check:github-actions,
check:ci-scope, check:gitleaks-pinned, check:ci-triage, check:pr-policy, check:gate-manifest,
check:skills, check:pr-mergeability, check:verification-plan, lint, typecheck, test,
check:rag:fixtures, check:medication-interactions, check:medication-lexicon-report
- failed: (none)
- not reached: (none)

Focused evidence on the changed contracts:

  • npx vitest run tests/guard-push.test.ts — 42/42 passed in a container with no gh installed, which is the environment that previously failed.

  • npx vitest run tests/ci-cache-safety.test.ts — 53/53 passed, including the new assertion that base-branch pushes key concurrency on github.run_id.

  • node scripts/guard-push.mjs --self-test — passed; the same injection was applied there, since the script's own self-test had the identical coupling.

  • node scripts/ci-triage.mjs --self-test — passed, extended with the skipped-baseline case (unbaselined classification, and the "did not exercise" sentence).

  • npm run check:github-actions — pin check passed.

  • npm run verify:ui when UI, routing, styling, browser behavior, reduced-motion, or forced-colors behavior changed

UI verification not run: no UI, routing, or styling surface is touched. The diff is two workflow files, two scripts and two test files.

  • npm run verify:release before release or handoff confidence claims

Not run: release confidence was not requested.

Risk and rollout

  • Risk: Medium, and concentrated in the concurrency change. It alters how many CI runs can execute at once on main: previously a burst of merges produced one run plus one survivor, now it produces one run per merge. That is the cost the existing in-file comment already accepts ("concurrent main runs, one per merge, each already scoped by the changes job"), but it is a real increase in runner usage during merge bursts and should be watched. The triage change adds one paginated job listing per triage comment, on a workflow that only fires when a PR's CI has already failed. The guard-push change is inert in production — callers pass no options and get the real probe.
  • Rollback: Revert any single commit independently — 5f9b4a5 (concurrency), 0916c9b (triage), 2e535bd (guard-push). Reverting the concurrency commit restores the shared CI-refs/heads/main group and the eviction behaviour; nothing else depends on it.
  • Provider or production effects: None. No provider call, deployment, migration, or production data access. The workflows changed are CI-only and do not touch deploy paths.
  • RAG impact: none; no RAG, retrieval, ranking, scoring, fixture, or answer-generation code is touched.

Notes

  • I did not split this into three PRs despite AGENTS.md "PR bundling" excluding operational-risk items from bundling. Two reasons, both worth a reviewer's judgement: this session is constrained to a single designated branch, and scripts/pr-policy.mjs only warns about bundling when operational risk mixes with clinical or UI changes, which this does not. Every item is its own commit so pre-merge revertibility is intact; say the word and I will split them.
  • The concurrency mechanism is GitHub's documented queue behaviour, and it is also established by elimination: with cancel-in-progress false for pushes, pending-eviction is the only remaining way GitHub cancels a run in a group. The observed timeline fits exactly — the four cancelled runs bracket the 16:24→17:27 window during which run 32391849411's release-browser-matrix held the group.
  • Not fixed here, and still owed: the document-viewer Linux visual baseline needs re-adopting from the visual-baseline artifact after PR Make document search on demand #2199 lands.

Generated by Claude Code

… cases can run
`inFlightCiGuard` probes for the `gh` binary and fails open before either injected
fetcher is consulted. The message-formatting case injected `prViewer` and
`runFetcher` but could not inject that probe, so it only ever exercised the code
under test on a machine that happened to have `gh` installed: green in CI, red in
any bare container, for no product reason.
`ghAvailable` now joins the other two as an injectable, defaulting to the real
probe. The formatting case and the script's own `--self-test` pass it, and a new
case pins the fail-open branch itself — asserting that neither fetcher is
consulted when `gh` is absent, which nothing covered before.
No behaviour change: production callers pass no options and get `ghIsAvailable`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HoyoNN7nCUVDLaE84k6e6c
…p evicting each other
`cancel-in-progress: ${{ github.event_name != 'push' }}` was added to stop a later
merge superseding an in-flight main run, and it does that. It does not stop the
other way a run dies: GitHub keeps at most ONE pending run per concurrency group,
so when merges land faster than a run completes, each newly queued main run
cancels the one already waiting. No supersession is involved, so the exemption
never sees it, and main kept landing unverified anyway.
Observed 2026-08-20: `a1c2ced`, `d745d15`, `97f6142` and `1cc0d29` were all
cancelled while a ~70-minute release-browser-matrix held the shared
`CI-refs/heads/main` group. A mobile-`/` CLS regression rode through that gap and
surfaced hours later on an unrelated PR (#2199), where it read as that PR's
failure. It is the same pattern the existing comment already documents from
2026-08-18 (23 of 30 main pushes cancelled) — the earlier fix simply treated the
wrong mechanism.
Pushes now key concurrency on `github.run_id`, exactly as workflow_dispatch and
schedule already do, so a merged commit is never queued behind or evicted by
another. `cancel-in-progress` stays: it is the line that states the intent, and it
remains the only protection if the group key ever changes.
Cost is the one the existing comment already accepts: concurrent main runs, one
per merge, each still scoped by the `changes` job.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HoyoNN7nCUVDLaE84k6e6c
…a baseline
CI is path-scoped, so a docs-only push to main reports `success` with Lighthouse,
Production UI and Build all skipped. The triage comment nevertheless printed
"Compared with main CI run #N (success)" and labelled the failure
"needs investigation", which reads as "main is green for this job" when main had
never measured it. That is how the mobile-`/` CLS regression was waved through on
PR #2199 — the bot cited run #12662, a docs-only merge that skipped Lighthouse
entirely — and it is the trap ledger #5DYBQQ already recorded from the other side.
The baseline's jobs are now read whatever its conclusion, not only when it failed.
A failing job the baseline did not execute is classified `unbaselined` and says so
in its own words, and the comparison sentence names the jobs that run did not
exercise. `classifyFailedJobs` takes the executed-name list as an optional fourth
argument: omitted means "could not establish", which keeps the previous
classification rather than inventing a verdict — so the `catch` path in the
workflow degrades exactly as before.
Cost is one extra paginated job listing per triage comment, on a workflow that
only runs when a PR's CI has already failed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HoyoNN7nCUVDLaE84k6e6c
@coderabbitai

coderabbitaiBot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in:5 minutes

Limit details: You’ve used the included review currently available. Your 87 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 11ef8cd1-96ec-4695-b828-966ce7aa45fb

📥 Commits

Reviewing files that changed from the base of the PR and between 9f98bbf and c74a049.

📒 Files selected for processing (6)
  • .github/workflows/ci-triage.yml
  • .github/workflows/ci.yml
  • scripts/ci-triage.mjs
  • scripts/guard-push.mjs
  • tests/ci-cache-safety.test.ts
  • tests/guard-push.test.ts

Comment @coderabbitai help to get the list of available commands.

@supabase

supabaseBot commented Aug 21, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@BigSimmo
BigSimmo marked this pull request as ready for review August 21, 2026 08:05

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:cf2865879c

ℹ️ 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 threadscripts/ci-triage.mjs Outdated
@BigSimmo

Copy link
Copy Markdown
OwnerAuthor

@codex resolve actionable Codex review findings for this pull request and current head using the repository instructions. This is the pull request's single automatic repair pass: do not perform a fresh review, create new standalone findings, or request another review. Work only the existing unresolved Codex threads on the current head. The only repository destination is BigSimmo/Database, and the only branch destination is the pull request head branch claude/pr-review-comments-yegd3q at starting commit cf28658; never publish fixes to a detached or synthetic work branch and never create a stacked pull request. Use the authenticated GitHub connector to commit each approved fix to BigSimmo/Database:claude/pr-review-comments-yegd3q, then verify that the pull request head contains the published commit before reporting success. Always fix P0 and P1 findings. For P2 and lower findings, fix only clear, scoped, low-risk issues; otherwise disposition them with a concise reason. For a fixed thread, reply with as the first line and as the second line. For a no-code disposition, use followed by . These result markers authorize the workflow to close that exact thread only after it verifies a fixed commit is the pull request head; a local-only commit is not a fix. If publication or verification fails, do not use either result marker, do not claim success, and leave the thread open with the blocker. If human input or new authorization is required, do the same. Finish only after every actionable thread is fixed or dispositioned and closed, or explicitly left open for a human decision. Do not update the branch from main, address unrelated reviews, broaden scope, or create more than one scoped fix commit. Do not use external APIs, paid services, credentials, dependency changes, or broad refactors unless explicitly authorized. Add targeted tests where behavior changes and run the narrowest relevant validation.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit:cf2865879c

ℹ️ 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".

`executedJobNames` excluded `skipped` by denylist, so every other conclusion
counted as evidence — including `cancelled`. A cancelled job completed no
verification, so a PR's Build failure would still be reported as
`needs-investigation` while citing a main run that never finished that check:
the same wrong claim the previous commit set out to remove, arrived at from a
different conclusion value. It is also the likeliest one here, since a cancelled
main run is this repo's common failure mode rather than a rarity.
Inverted to an allowlist — `success`, `failure`, `neutral` — because the two
mistakes are not symmetric. Calling a job that ran "unbaselined" only says the
comparison is silent there, which costs a reader nothing; calling a job that did
NOT run "baselined" implies main covered the failure. An allowlist therefore
fails in the harmless direction for `timed_out`, `stale`, `action_required` and
any conclusion GitHub adds later, instead of silently treating each new state as
evidence.
Self-test covers cancelled, timed_out, stale, action_required, an unrecognised
future conclusion, and the end-to-end case that a cancelled baseline job yields
`unbaselined`.
Raised by Codex review on this PR (P2).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HoyoNN7nCUVDLaE84k6e6c
@BigSimmo
BigSimmo merged commit af2075a into mainAug 21, 2026
32 checks passed
@BigSimmo
BigSimmo deleted the claude/pr-review-comments-yegd3q branch August 21, 2026 08:43
BigSimmo pushed a commit that referenced this pull request Aug 21, 2026
Resolves a conflict in the guard-push pair, where PR #2209 landed the same fix
independently: `ghAvailable` injected into `inFlightCiGuard` so the fail-open
`gh --version` probe stops short-circuiting the injected fetchers.
Took main's version of both files wholesale rather than hand-merging two
spellings of one change. It is a strict superset of the local one: it also
injects the probe into `selfTest`'s `mockBlockedGuard`, and adds a
fails-open-when-gh-is-unavailable case whose fetchers throw if consulted.
Nothing local was lost in substance.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014uzqrokJW55EN9vbZhFyXz
Sign up for freeto 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.

2 participants

@BigSimmo@claude