Skip to content

ci(vercel): skip builds a push cannot affect - #555

Draft
alukach wants to merge 1 commit into
mainfrom
worktree-vercel-ignore-build
Draft

alukach wants to merge 1 commit into
mainfrom
worktree-vercel-ignore-build

Conversation

@alukach

@alukach alukach commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Why

Chasing the Vercel cost bump that starts around Aug 16. Build CPU Minutes were $3.81 of the $17.54 peak day (Aug 24), and a large share of those minutes were spent building bundles the diff could not change.

Two Vercel projects build from this repo — the app and the Storybook published at ui.source.coop — and vercel.json only disables git deploys for main, so every push to every branch builds both. From the Storybook work in late August:

PR Files touched Built
#521 .storybook/main.ts + 3 .stories.tsx app and Storybook
#534 .storybook/preview.tsx (one file) app and Storybook
#536 3 .stories.tsx app and Storybook

That landed the same week PR volume peaked (8 PRs on Aug 24, 15 on Aug 28).

What

ignoreCommand in vercel.json points both projects at scripts/vercel-ignore-build.sh, which routes changed paths to the bundles that can actually see them. vercel.json is shared by both projects, so each names itself with a VERCEL_IGNORE_SCOPE variable in its own Vercel project settings.

It skips only what it can prove is unnecessary:

  • An unrecognised path counts as affecting both bundles, so adding a directory never silently stops building it.
  • A project with no VERCEL_IGNORE_SCOPE builds every push, exactly as today.
  • The diff base is the project's last deployed commit, not HEAD^. Vercel builds only the head commit of a push, so HEAD^ sees the final commit alone — on a multi-commit push whose last commit is story-only it would skip a build whose earlier commits did change the bundle, leaving a stale preview on a PR under review. Where that SHA is missing or unresolvable, it builds.

Deploying

This does nothing until both projects get their variable — that is deliberate, since the safe default is to build:

  • source-cooperativeVERCEL_IGNORE_SCOPE=app
  • the Storybook project → VERCEL_IGNORE_SCOPE=storybook

Testing

bash scripts/vercel-ignore-build.sh --self-test — 13 routing cases, including story-only, route-only, component+story, docs-only, lockfile, public/, an unrecognised path, and an unknown scope. shellcheck is clean.

Also exercised end-to-end against real commits in a scratch branch: a story-only push skips the app build and runs Storybook's; the inverse for a route-only push; a bogus or absent previous-deployment SHA builds. The multi-commit case above was a real defect caught by that test, not a hypothetical — a HEAD^ fallback skipped a build it should have run, which is why the fallback is gone.

Docs

No UI change, so there are no stories to link. Checked both external repos: this touches build gating only, so nothing in docs.source.coop using-source/ or about-source/ describes it, and it moves no decision shared with the data proxy — the data.source.coop ADRs on authorization, STS credentials, API keys and federation are untouched. CONTRIBUTING.md gains a "Skipped builds" section, and the comment in ui-deploy.yaml calling PR previews "free" is corrected — they cost build minutes, which is how this went unnoticed.

Not in scope

Three other cost findings from the same investigation, to follow separately: a missing sitemap.xml that robots.ts advertises, an uncached feed.xml that scans every public product per request, and images.remotePatterns set to hostname: "*".

🤖 Generated with Claude Code

https://claude.ai/code/session_01RsUkHcqFckbNcdiVxunhrg

Two Vercel projects build from this repo, so every push builds both. Most
pushes cannot change one of them: #521, #534 and #536 each touched only
`.stories.tsx` / `.storybook/` files and still rebuilt the whole Next.js app,
and #534 did it for a single-file change.

`ignoreCommand` in vercel.json points both projects at one script, which routes
changed paths to the bundles that can see them. vercel.json is shared, so each
project names itself with a VERCEL_IGNORE_SCOPE environment variable; a project
without one builds every push exactly as it does today.

It skips only what it can prove is unnecessary. Unrecognised paths count as
affecting both bundles, so adding a directory never silently stops building it.
The diff base is the project's last deployed commit rather than HEAD^ — Vercel
builds only the head commit of a push, so HEAD^ sees the final commit alone and
would skip a build whose earlier commits did change the bundle, leaving a stale
preview on a PR under review. Where that SHA is missing, it builds.

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

vercel Bot commented Sep 14, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
source-coop-ui Ready Ready Preview Sep 14, 2026 7:21pm UTC
source-cooperative Ready Ready Preview Sep 14, 2026 7:21pm UTC

Request Review

@claude

claude Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Claude finished @alukach's task in 3m 5s —— View job


✅ No blocking issues — safe to merge.

Traced classify()/decide() in scripts/vercel-ignore-build.sh against every case pattern and the self-test's 13 scenarios by hand (couldn't execute the script itself in this sandbox — execution requires an approval this run didn't have). The routing logic, the printf '%s\n' trailing-newline guard so while read doesn't drop the last file, the VERCEL_GIT_PREVIOUS_SHA verifiability check, and the fail-open defaults (unknown scope, unresolvable base, unrecognised path → build) all check out. No bugs found in the diff.

Two edge cases worth watching, neither a bug in this PR and neither blocking:

  • scripts/vercel-ignore-build.sh:29scripts/* is classified app-only. A future commit that only edits this script (e.g. a fix to classify() itself) would skip the Storybook build on that same commit, so a regression in the storybook-gating logic could go unverified against a real Storybook build with no signal (a skip isn't a failure). Today's diff also touches vercel.json, which is catch-all (both), so this PR itself builds both — it's only future single-file edits to the script that hit this.
  • scripts/vercel-ignore-build.sh:29src/app/* is assumed app-only on the reasoning "stories import components and lib only." That holds today (verified: no .stories.tsx imports anything from src/app/), but src/app/(marketing)/HeroGlobe.tsx and CaseStudyCarousel.tsx are already non-route components living under src/app/. If either ever gains a story — plausible, given this repo's own "every UI change lands in Storybook" rule — its Storybook build would be silently skipped on component-only changes until the story file itself is touched.

Docs

Diff is CI/build-gating only — no user-facing flow from docs.source.coop and no shared decision from data.source.coop's adrs/ (authorization, STS, API keys, federation) moves here. The PR description already states this explicitly and gives the reasoning, satisfying CLAUDE.md's "Docs and ADRs" requirement.


💰 Estimated review cost: $0.56 · 3m05s · 24 turns

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant