Skip to content

[SURF-2672] fix(release): purge jsDelivr until @latest serves the new version - #77

Merged
lilnasy merged 1 commit into
mainfrom
fix/release-repurge-until-fresh
Nov 11, 2026
Merged

lilnasy merged 1 commit into
mainfrom
fix/release-repurge-until-fresh

Conversation

@lilnasy

@lilnasy lilnasy commented Nov 11, 2026 •

Copy link
Copy Markdown
Member

Why

The v1.1.19 release run purged @latest/surface_tag.min.js once. A CDN server refilled the file seven seconds later from 1.1.18, because jsDelivr had not yet picked up the new release. Customers on that server would have loaded the old Surface Tag for up to 12 hours. A manual purge fixed it.

The companion PR splits the publish workflow in surface_forms so a failure like this is visible per step.

The self review ran after this PR merged. The follow-up PR holds its fixes and replaces the purge loop.

What changes

  • After the first purge, the release workflow waits until all four @latest files report the new version. It purges any stale file again on each check.
  • It then purges every file one more time.
  • The run fails when a file still reports an older version after 5 minutes.

How it works

Before: one purge. A CDN server that refilled right after it cached the old version.

flowchart LR
  r["Cut release"] --> p["Purge once"] --> d["Done"]
  p -.-> s["CDN server refills from the old version"]
Loading

After: purge until every file reports the new version, then purge once more.

flowchart LR
  r["Cut release"] --> p["Purge"] --> w["Wait for @latest to serve the new version"] --> a["Purge again"]
  w -->|stale file| x["Purge that file"] --> w
Loading
  • The wait reads the version jsDelivr reports in the x-jsd-version response header. It does not compare file contents.
  • Purging moved into one script that both the purge steps and the wait step call. A failed purge fails the run.

Risk

  • The new steps have not run in CI. The next Surface Tag release is their first run.
  • A release now takes at least 10 seconds longer, and up to 5 minutes longer when the CDN is slow.
  • A failed wait fails the run after the release is already cut. The release itself is unaffected; purge the stale files by hand.
  • The job now checks out the repo to run the purge script.
  • The two PRs are independent. Either can merge first.

QA

Exercised locally:

  • The workflow file parses, every step script passes a shell syntax check, and every step reference resolves.
  • The version check returned 1.1.19 for all four live @latest files and "unavailable" for a file that does not exist.

Left for the reviewer: nothing before merge. Watch the first real release after merge.

Known gaps

  • actionlint was not run.
  • The wait checks from one CDN location. Different CDN servers held different stale files during the incident. The final purge covers servers the wait did not reach.
Implementation details

Self review

The review ran after this PR merged. The fixes are in the follow-up PR.

  • 15 findings, 12 fixed in the follow-up: the wait window was shorter than jsDelivr's 10-minute version-list cache; re-purging every round trips the purge throttle; re-running a failed run cut another release; the purge response was discarded so throttled purges looked green; the first failed purge aborted the rest; no timeouts; purging @main did nothing useful; the timeout message said 5 minutes for an ~8-minute loop; probes downloaded whole bundles instead of HEAD; checkout ran before cutting the release; the release log claimed @latest already resolved; a comment narrated the steps. 3 declined: freshness is sampled from one CDN provider (one runner can't reach every provider; the second purge covers the rest); longer runs widen pending-run cancellation (needs three pushes within minutes, and Step 1 serializes publishes); the version probe is duplicated in surface_forms' verify (kept as an independent check from the publisher's side).

Incident

  • The run purged @latest/surface_tag.min.js at 23:12:11 UTC.
  • A CDN server refilled the file at about 23:12:18 from 1.1.18. Its header read Original file: /gh/trysurface/scripts@1.1.18/surface_tag.js.
  • Different CDN servers held different stale files. Later, surface_embed_v1.min.js was stale from Frankfurt.
  • The cached copies would have stayed for up to 12 hours (s-maxage=43200).
  • The surface_forms publish run failed its verify step on the stale file.

Changes

  • .github/scripts/purge-jsdelivr.sh purges @latest and @main for each file it is given. It uses curl --retry 3 and exits 1 with an ::error:: line on the first failed purge.
  • .github/workflows/release-and-purge.yml:
    • FILES is workflow-level env and lists the four files.
    • The job starts with actions/checkout@v4 so the script is on disk.
    • "Cut a release for the pushed commit" has id: release and outputs version (the tag without the v).
    • "Purge jsDelivr cache" calls the script with all four files.
    • "Wait for @latest to serve vX" runs up to 30 rounds, 10 s apart. Each round reads x-jsd-version for each file, prints it, and calls the script with the stale files. It writes one line to the run summary on success.
    • "Purge jsDelivr cache again" calls the script with all four files.

Validation

  • js-yaml parses the workflow.
  • bash -n passes on every step's run body and on the purge script.
  • steps.release.outputs.version resolves to the release step's output.
  • The x-jsd-version read returned 1.1.19 for all four live @latest files and nothing for a missing file, which the step prints as "unavailable".

@notion-workspace

Copy link
Copy Markdown

@lilnasy lilnasy changed the title [SURF-2672] fix(release): purge jsDelivr until @latest serves the new version [SURF-2672] fix(release): purge jsDelivr until @latest serves the new version Nov 11, 2026
@lilnasy
lilnasy marked this pull request as ready for review November 11, 2026 02:01
@lilnasy
lilnasy merged commit 6d333ec into main Nov 11, 2026
1 check passed
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