Skip to content

ci: run identical checks on PRs and main to keep them in lockstep#261

Merged
trevor-coleman merged 7 commits into
mainfrom
claude/nifty-dijkstra-9291T
Jun 4, 2026
Merged

ci: run identical checks on PRs and main to keep them in lockstep#261
trevor-coleman merged 7 commits into
mainfrom
claude/nifty-dijkstra-9291T

Conversation

@trevor-coleman

Copy link
Copy Markdown
Collaborator

Closes #250.

The problem

PRs and main were validated by two different pipelines running different checks, so a PR could go green and then break on merge:

PR checks main / release checks
Where CircleCI (test_and_build) GitHub Actions (release.yml)
Steps install → lint → ci:build → test install → ci:build → changesets (yarn version / yarn release)

Two gaps fall out of this:

  1. The changeset version/release step only ever ran on main, so errors in it were invisible until after merge.
  2. The main pipeline wasn't even a superset of PR checks (no lint, no test).

That's exactly how #247 broke: it deleted the @infinitered/react-native-mlkit-docs package but left a changeset referencing it. CircleCI never runs changesets, so the PR was green; on merge, changeset version failed on main and needed follow-up fixes (#251, #252).

The fix — one source of truth

  • New .github/workflows/ci.yml with a single verify job: install → lint → changeset statusci:build → test. It runs on every pull_request and is exposed via workflow_call.
  • release.yml now calls that workflow as a verify job and gates the version/release job behind it (needs: verify), so main runs the exact same verify job as PRs before anything is versioned or published.
  • changeset status validates that every changeset references a real workspace package. Verified locally: it passes on the current changeset and fails (exit 1) when pointed at the deleted react-native-mlkit-docs package — i.e. it would have caught docs: update contribution docs and remove docusaurus #247 at PR time.
  • CircleCI drops the now-duplicate test_and_build job and keeps only docs publishing, which depends on the Infinite Red publish-docs orb + CircleCI-managed SSH access to ir-docs.

⚠️ Reviewer action needed (branch protection)

If main's required status checks currently require the CircleCI test_and_build check, that check no longer runs. Update the branch protection rules to require the new GitHub Actions verify / "Lint, validate changesets, build, and test" check instead, otherwise PRs may hang waiting on a check that never reports.

Notes / open questions

  • This makes GitHub Actions the single source of truth for code checks and leaves CircleCI for docs only. If you'd rather keep CircleCI as the canonical runner, the alternative is to mirror lint + changeset status + test into it instead — happy to flip the approach.
  • No changeset added: this is CI/tooling config only, no published package changes.

https://claude.ai/code/session_01HWaRWqpMHzzeTSJeqTeV75


Generated by Claude Code

PRs were validated by CircleCI (lint + ci:build + test) while main was
validated by the GitHub Actions release workflow (ci:build only, then
changesets). Because the changeset version/release step only ever ran on
main, a PR could go green and then break on merge -- e.g. #247 left a
changeset pointing at the deleted react-native-mlkit-docs package, which
passed PR CI but failed `changeset version` on main.

- Add .github/workflows/ci.yml: a single `verify` job (install, lint,
  changeset status, ci:build, test) that runs on every pull_request and is
  exposed via workflow_call.
- release.yml now calls that workflow as a `verify` job and gates the
  version/release job behind it (`needs: verify`), so main runs the exact
  same checks as PRs before anything is versioned or published.
- `changeset status` catches changesets that reference missing/renamed
  packages at PR time (verified: it fails on a docs-package reference).
- Drop the now-duplicate test_and_build job from CircleCI; CircleCI keeps
  only docs publishing, which depends on its orb + ir-docs SSH access.

Closes #250

https://claude.ai/code/session_01HWaRWqpMHzzeTSJeqTeV75
@changeset-bot

changeset-bot Bot commented Jun 4, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: ed18685

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Comment thread .github/workflows/ci.yml Fixed
claude added 6 commits June 4, 2026 16:17
- `changeset status` failed on PRs with "Failed to find where HEAD
  diverged from main" because a fresh Actions checkout has no local
  `main` branch. Pass `--since=origin/main` so it diffs against the
  remote-tracking ref (fetch-depth: 0 already provides the history).
  Verified it still fails on a changeset referencing a missing package.
- Add an explicit `permissions: contents: read` block, resolving the
  CodeQL "Workflow does not contain permissions" finding. The verify
  job only reads the repo; publishing stays in release.yml.

https://claude.ai/code/session_01HWaRWqpMHzzeTSJeqTeV75
This PR only touches CI configuration (no published package changes),
so it carries an intentionally empty changeset. This satisfies the new
`changeset status` gate, which requires every PR with changes to either
declare a release or explicitly opt out via an empty changeset.

https://claude.ai/code/session_01HWaRWqpMHzzeTSJeqTeV75
`changeset status` couples two behaviors: validating that changesets
reference real packages, and requiring every PR with changes to include
a changeset. Only the former is needed for #250, and the latter forced
empty changesets on CI/docs/chore PRs (and behaved inconsistently between
local and the PR merge-ref checkout).

changesets has no config flag to disable the "changed but no changeset"
error, so replace the step with scripts/validate-changesets.mjs, which
uses @changesets/get-release-plan (already a devDependency) to assemble
the release plan. That throws on a changeset referencing a missing
package (catching #247) but does not require a changeset to exist and
needs no git history -- so fetch-depth: 0 and the empty changeset are
both dropped.

https://claude.ai/code/session_01HWaRWqpMHzzeTSJeqTeV75
Revert the custom validation script in favor of `changeset status`, which
requires a changeset when a versionable package changes (the behavior we
want) and still fails on changesets referencing a missing package (#247).

Two adjustments make it robust:

- Set `privatePackages.version: false` in .changeset/config.json. By
  default changesets treats private packages as versionable, so changes
  to the private demo app (example-app) and config packages demanded a
  changeset. Those are never published, so they are now exempt; the six
  published @infinitered/* modules still require one.

- Run the changeset check before `yarn lint`. Lint runs `prettier
  --write`, which reformats drifted files in place and made changesets
  think example-app changed. Checking the pristine, just-checked-out tree
  avoids that phantom change.

Note: root-level files (.github, .circleci, etc.) are not part of any
workspace package, so CI/config-only PRs never require a changeset.

https://claude.ai/code/session_01HWaRWqpMHzzeTSJeqTeV75
CI ran `yarn lint`, whose example-app script does `eslint --fix` and
`prettier --write`, silently reformatting files in the runner instead of
failing on drift. Make CI verify instead of fix:

- Add `lint:ci` to example-app (eslint without --fix, prettier --check)
  and a matching `format:check` script.
- Add root `ci:lint` that lints the modules via turbo (their
  `expo-module lint` is already non-mutating) and runs example-app's
  check. No --no-cache: the example-app check runs outside turbo and
  module lint is non-mutating and input-keyed, so turbo's cache stays
  correct.
- Point the CI workflow at `yarn ci:lint`. `yarn lint` still auto-fixes
  locally.
- Format the two drifted example-app files so the check passes.

https://claude.ai/code/session_01HWaRWqpMHzzeTSJeqTeV75
The job name is what branch protection's required-status-check rule
matches on. Use a short, stable 'verify' instead of a descriptive
sentence so the rule doesn't need re-updating if the description changes.

https://claude.ai/code/session_01HWaRWqpMHzzeTSJeqTeV75
@trevor-coleman trevor-coleman merged commit c08d53b into main Jun 4, 2026
5 checks passed
@trevor-coleman trevor-coleman deleted the claude/nifty-dijkstra-9291T branch June 4, 2026 18:50
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.

CI should fail consistently across PRs and main

3 participants