Skip to content

internal(ci): Cut CircleCI critical path and skip tests on docs-only changes - #4112

Merged
ntucker merged 4 commits into
masterfrom
claude/project-thread-gs5y0j
Oct 3, 2026
Merged

ntucker merged 4 commits into
masterfrom
claude/project-thread-gs5y0j

Conversation

@ntucker

@ntucker ntucker commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

Requested by Nathaniel · project thread

Motivation

On package PRs, CircleCI takes about 3.75 min from push to the last green check. The critical path is setup → setup-esmodule-types → esmodule-types-*, and the middle job is mostly ci:build:legacy-types (about 48s on 4 vCPU) plus job spin-up and workspace attach/persist. Website/docs-only PRs (for example #4104) still run the full unit, node, lint and typecheck matrix, which takes about 2.3 min even though none of it can be affected.

Solution

  • Legacy types are faster to build.
    • scripts/build-legacy-types.sh builds each TS version concurrently. It calls downlevel-dts directly instead of booting yarn for every step, and it copies only .d.ts files from src-*-types.
    • Each version's output dir still gets the same sequence: earlier versions' custom types, then the downleveled lib, then its own custom types.
    • rest's inline script is split into two run-p halves, using a new g:runp helper.
    • In CI, ci:build:legacy-types builds only the TS >= 4.0 outputs: endpoint, normalizr and rest, with LEGACY_MIN_TS=4.0. The oldest TS in the esmodule-types matrix is 4.0, so nothing in CI reads ts3.4.
    • Release builds still emit every version. All 14 ts* dirs are byte-identical to master's output, and the CI step went from 48s to about 15s locally.
    • The script now uses set -e. Before, a failed downlevel-dts or copy could let build:types finish with incomplete ts* dirs; now it fails.
  • No separate setup-esmodule-types job. When the esmodule flag is set, setup runs ci:build:setup:esmodule, which builds types and then legacy types, in parallel with the test lib build. That removes a job hop of about 75s, and esmodule-types now runs alongside the unit tests.
  • New tests relevance flag.
    • lint, typecheck, unit_tests and node_matrix halt when every changed path is docs, website or tooling: website/ (except website/src/components/Playground/, which has unit tests), docs/, .changeset/, .cursor/, .agents/, .claude/, .github/, or a root *.md.
    • When both flags are false, setup also halts before install.
    • On the default branch, both flags are always true, because a push there can carry several commits.
    • Missing flags still fail open, and setup's own later steps read the flags from BASH_ENV.
  • The esmodule regex now also covers root package.json, root tsconfig*.json, babel.config.js, .yarnrc.yml and scripts/.
  • The relevance checks diff with --no-renames, so moving a file out of packages/ still counts as a package change.
  • The checks grep a temp file instead of piping into grep -q. CircleCI runs bash with pipefail, where a SIGPIPE could flip a check. Here-strings aren't an option either, because CircleCI treats << as its own parameter syntax.
  • Updated .cursor/rules/ci-config.mdc.

How I validated it

  • Compared the legacy-types output against a baseline from master, for both the full release set and the CI subset.
  • Ran the detection step under bash -eo pipefail for docs-only, Playground, packages, scripts, rename, empty-diff and default-branch cases.
  • Ran a Fable adversarial review over the whole diff and fixed its findings.
  • CircleCI is green on earlier heads of this PR.

Open questions

  • None. The adversarial review found that setup-esmodule-types is not among the required status checks, so removing it doesn't block merging.

🤖 Generated with Claude Code

https://claude.ai/code/session_019PoENobJ7nEmcJd4ZB5LwC

…changes

- Build legacy TS types inside `setup` (only when esmodule-relevant) and
  drop the separate `setup-esmodule-types` job hop.
- Build each legacy TS version concurrently (output is byte-identical),
  and drop topological ordering since each package only reads its own lib.
- Halt lint/typecheck/unit_tests/node_matrix when only docs, website
  (except Playground) or tooling paths change; halt `setup` too when
  nothing downstream is relevant.
- Treat root package.json and scripts/ as esmodule-relevant.
- Use here-strings in relevance checks so pipefail+SIGPIPE can't flip them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019PoENobJ7nEmcJd4ZB5LwC
@changeset-bot

changeset-bot Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: a22b88f

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

@vercel

vercel Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
docs-site Ignored Ignored Preview Oct 3, 2026 10:10pm UTC

Request Review

@ntucker ntucker self-assigned this Oct 3, 2026

@ntucker ntucker left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Staff review (tip 3f418100) — LGTM; no CHANGE_THIS_PR.

Critical path fold is sound: setup already persists project/packages (so ts* rides along without the old job’s extra persist), ci:build:types-and-legacy keeps types before downtype, and test-lib stays parallel with that chain. Fail-open on missing flags, here-strings under pipefail, Playground carve-out (tests otherwise skip only when every path is docs/website/tooling), and expanding the esmodule regex to root package.json + scripts/ are the right seams. Concurrent build-legacy-types.sh matches the old per-dir overlay order (earlier custom → downtype → own custom); rest’s g:runp split preserves the same overlays.

FOLLOW_UP (ops, not a code change): before merge, drop ci/circleci: setup-esmodule-types from branch protection if it is still required — same open question as in the PR body. Soft watch on first package PRs: -j unlimited legacy work now shares the large setup box with types + test-lib; if wall time or OOM erodes the hop savings, cap parallelism rather than bringing the hop back.

Hold merge until Bugbot success/waive and CircleCI green on this tip.

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Size Change: 0 B

Total Size: 103 kB

ℹ️ View Unchanged
Filename Size
examples/test-bundlesize/dist/App.js 1.46 kB
examples/test-bundlesize/dist/polyfill.js 307 B
examples/test-bundlesize/dist/rdcClient.js 10.9 kB
examples/test-bundlesize/dist/rdcEndpoint.js 8.07 kB
examples/test-bundlesize/dist/rdcNextjs.js 12.3 kB
examples/test-bundlesize/dist/rdcPipeableStream.js 9.63 kB
examples/test-bundlesize/dist/react.js 59.7 kB
examples/test-bundlesize/dist/webpack-runtime.js 784 B

compressed-size-action

CircleCI parses `<<` as parameter syntax, so the here-strings likely
kept the pipeline from compiling. Grep a temp file instead (still no
pipes, so pipefail can't flip a check).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019PoENobJ7nEmcJd4ZB5LwC

@ntucker ntucker left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Staff review (tip 58b9ecf7) — LGTM; no CHANGE_THIS_PR.

Delta since 3f418100: swap here-strings for printf → /tmp/ci-changed-files then grep. Correct under CircleCI YAML (<< is parameter syntax); still avoids pipefail/SIGPIPE flips. Prior fold/-j unlimited/fail-open/Playground carve-out/esmodule regex notes hold.

FOLLOW_UP (ops, unchanged): drop ci/circleci: setup-esmodule-types from branch protection if still required; soft-watch first package PRs for setup box contention under -j unlimited. Soft: PR body still says here-strings — update when convenient.

Hold merge until Bugbot success/waive and CircleCI green on this tip.

@codecov

codecov Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.84%. Comparing base (8ac96f8) to head (a22b88f).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #4112   +/-   ##
=======================================
  Coverage   97.84%   97.84%           
=======================================
  Files         156      156           
  Lines        3057     3057           
  Branches      612      612           
=======================================
  Hits         2991     2991           
  Misses         18       18           
  Partials       48       48           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- CI builds legacy types only for TS >= 4.0 (endpoint, normalizr, rest);
  nothing in the esmodule-types matrix reads ts3.4. Release builds are
  unchanged (all 14 output dirs still byte-identical).
- build-legacy-types.sh calls downlevel-dts and cp directly instead of a
  yarn boot per step, and uses set -e.
- Relevance checks write git diff straight to the file and use grep exit
  status; setup's later steps read the flags from BASH_ENV.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019PoENobJ7nEmcJd4ZB5LwC

@ntucker ntucker left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Staff · tip 16300da2 · LGTM + FOLLOW_UP (no CHANGE_THIS_PR)

Delta since 58b9ecf7: relevance checks write git diff straight to /tmp/ci-changed-files and invert grep; setup later steps read flags from BASH_ENV (cache files stay for downstream). ci:build:legacy-types narrows to --include endpoint/normalizr/rest with LEGACY_MIN_TS=4.0 (drops -R --from react/rest/graphql and -j unlimited). build-legacy-types.sh uses set -e, direct downlevel-dts/cp, and below_min — rest’s own legacy scripts are unchanged.

That trim matches the matrix: oldest tested TS is 4.0; react/graphql/core only emitted ts3.4 under the old recursive walk. Pipefail/here-string soft notes from the prior tip are addressed in code + body.

FOLLOW_UP (ops, not this PR): drop required check ci/circleci: setup-esmodule-types if branch protection still lists it.

Hold merge until Bugbot + CircleCI are green on 16300da2 (setup already passed; esmodule-types/unit matrix still running — good live check of the include/LEGACY_MIN_TS path).

ntucker commented Oct 3, 2026

Copy link
Copy Markdown
Collaborator Author

Here's how I handled each Staff review item. All three reviews were LGTM with no CHANGE_THIS_PR.

  • Drop ci/circleci: setup-esmodule-types from branch protection (raised in all three reviews). Not a code change: it's a repository setting an admin has to change before merge. It's also listed under Open questions in the PR body.

  • Soft-watch -j unlimited contention on the setup box (3f418100). Handled in this PR (16300da):

    • ci:build:legacy-types no longer uses -j unlimited.
    • It now covers only endpoint, normalizr and rest for TS 4.0 and newer (--include with LEGACY_MIN_TS=4.0).
    • It calls downlevel-dts directly instead of through yarn.

    That leaves far less CPU work competing with the test lib build: the step went from 30s to 17s locally. No follow-up needed.

  • PR body still mentions here-strings (58b9ecf7). Fixed: the description now says the checks grep a temp file.

CircleCI is green on 16300da and Bugbot passed.


Generated by Claude Code

- Diff with --no-renames (and quotePath off) so moving a file out of
  packages/ into docs/website still runs tests.
- Run every job on the default branch instead of diffing HEAD~1, which
  misses earlier commits of a multi-commit (rebase-merge) push.
- Treat root tsconfig*.json, babel.config.js and .yarnrc.yml as
  esmodule-relevant.
- Copy only .d.ts files from src-*-types into published ts* dirs, as
  copyfiles did (portable find/cp, no GNU --parents).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019PoENobJ7nEmcJd4ZB5LwC
@ntucker
ntucker merged commit de55259 into master Oct 3, 2026
25 checks passed
@ntucker
ntucker deleted the claude/project-thread-gs5y0j branch October 3, 2026 22:57
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.

2 participants