Skip to content

fix(cdn): announce cdn.build_complete from every build trigger - #177

Merged
ABB65 merged 1 commit into
mainfrom
fix/cdn-build-complete-webhook
Jul 29, 2026
Merged

fix(cdn): announce cdn.build_complete from every build trigger#177
ABB65 merged 1 commit into
mainfrom
fix/cdn-build-complete-webhook

Conversation

@ABB65

@ABB65 ABB65 commented Jul 29, 2026

Copy link
Copy Markdown
Member

Problem

cdn.build_complete was emitted from one place — the manual-rebuild endpoint (cdn/builds/trigger.post.ts). The push-triggered path (webhooks/github.post.tsrunCDNBuild) and its catch-up builds never emitted it.

That event is not a nicety. For a consuming site it is the entire cache-invalidation story: the delivery CDN is a data plane, and a site that caches its renders (ISR, durable edge cache, or a deploy-independent KV such as Netlify Blobs) keeps serving pre-build payloads until its own TTL lapses. A site redeploy does not fix it — those caches outlive deploys by design, which is exactly what "durable" and "shared blob store" mean.

Evidence (staging — Lanista/collabers)

The CDN itself was never wrong. Git ↔ R2 matched entry-for-entry, and the selective builds produced bundles byte-identical to a full rebuild's (164,616 B across both locales). But the site kept serving pre-save content until someone pressed Rebuild now.

webhook_deliveries shows why — 10 deliveries, all 10 matching a manual build, none for the ~10 push-triggered ones:

delivery matching build
29 Jul 14:03:06 14:01:13 manual
17 Jul 01:11:09 01:09:25 manual
16 Jul 23:12:24 23:10:33 manual
16 Jul 16:15:58 16:14:09 manual
16 Jul 14:44:29 14:42:40 manual

cdn_builds mirrors it: every content session ends with a manual rebuild. The rebuild button was doing the purge, not the rebuild.

Change

Move the emit into runCDNBuild — the single path every trigger goes through — so manual, webhook and catch-up builds all announce themselves. Catch-up builds move the CDN to a newer commit, so they need it just as much.

Tests

4 new runner tests: push-triggered emit, catch-up emit, failed-build emit, no-op silence. The integration test now asserts the endpoint delegates workspaceId and does not emit directly.

pnpm test 1095/1095 · pnpm lint 0 errors · nuxt typecheck clean.

Not in this PR

Two ordering issues found in executeCDNBuild while tracing this, both real but separate:

  1. _manifest.json is uploaded first and _bundle/*.json last — measured 105 s apart on a full rebuild (R2 LastModified 11:01:1911:03:04), ~24 s on a selective one. For that window the CDN advertises a content version it has not published yet.
  2. _media_manifest.json is deleted by the step-7 sweep (it is not in uploadedPaths until step 8) and re-uploaded ~1 s later. The SDK throws on non-2xx and MediaAccessor caches the result for the instance's lifetime, so a lambda booting in that window is poisoned for good.

`cdn.build_complete` was emitted by the manual-rebuild endpoint alone, so a
push-triggered build refreshed the CDN and told nobody. For a consuming site
that is the whole invalidation story: the delivery CDN is a data plane, and a
site caching its renders (ISR, durable edge, or a deploy-independent KV like
Netlify Blobs) keeps serving pre-build payloads until its TTL lapses. A site
redeploy does not help — those caches outlive deploys by design.

Observed on staging (Lanista/collabers): every content save produced a correct
selective build whose artifacts matched a full rebuild byte for byte, yet the
site kept serving pre-save content until someone pressed "Rebuild now". The
webhook_deliveries history shows it exactly — ten deliveries, all ten matching a
manual build, none for the ~ten push-triggered ones. The rebuild button was
doing the purge, not the rebuild.

Move the emit into runCDNBuild, which every trigger goes through, so manual,
webhook and catch-up builds all announce themselves; catch-up builds move the
CDN to a newer commit and need it just as much. The payload shape is unchanged —
consumers parse those fields. A clean 0-file no-op (the content-less push
short-circuit) stays silent: nothing was published, so nothing needs purging.
Failed builds are still announced so the consumer can react.
@ABB65
ABB65 merged commit 11fb079 into main Jul 29, 2026
2 checks passed
@ABB65
ABB65 deleted the fix/cdn-build-complete-webhook branch July 29, 2026 16:35
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