Skip to content

fix(ci): make generated artifacts deterministic; unblock freshness + smoke gates#25

Merged
just3ws merged 3 commits into
masterfrom
ci/freshness-fetch-depth
Jul 8, 2026
Merged

fix(ci): make generated artifacts deterministic; unblock freshness + smoke gates#25
just3ws merged 3 commits into
masterfrom
ci/freshness-fetch-depth

Conversation

@just3ws

@just3ws just3ws commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Problem

The Pages deploy was silently blocked at the validate:generated_freshness gate. Diagnosed from CI's actual output (local always passed). Two independent causes, uncovered in sequence:

  1. Non-deterministic generated artifacts. community_stories.yml and interview_topics.yml select interview IDs via sort_by(recorded_date).first(N). recorded_date has ties (e.g. 7 interviews on 2023-10-23), and sort_by is not stable — so which IDs land in first(N) at a tie boundary was not a pure function of the data, and differed between the dev and CI Ruby builds. Ruled out libyaml line-wrapping (my first theory): interviews.yml has 145 wrapped lines and never drifted, so wrapping is platform-stable here.
  2. Playwright smoke cold-start timeout. Once freshness passed, CI reached the smoke step and failed at status 124 (~21s in): the first pwcli command cold-fetches @playwright/cli + its browser under a 20s per-command cap, before any assertion runs.

Fix

  • Deterministic sort: add an id tiebreaker to both recorded_date sorts → committed artifacts are a pure function of their input, independent of sort implementation.
  • Instrument the gate: validate_generated_freshness.rb now prints the actual git diff on failure, not just filenames — the missing diff is why this was CI-only guesswork.
  • Smoke timeout: raise PWCLI_CMD_TIMEOUT to 180s for the CI smoke step only (local keeps the fast 20s default). Applied to validate.yml + deploy.yml.
  • Checkout depth: keep fetch-depth: 0 (full history for last_modified date derivation) — defensive hardening, harmless.

Verified

Freshness gate green in CI (the run progressed past validate to the smoke step). Smoke-timeout fix verification in progress on the latest push.

Merge note

This is the keystone. Merge first — it unblocks both gates and lets the deploy (same smoke step) complete. PRs #26 and #27 branch off pre-fix master and should be rebased onto updated master after this merges, or their CI will show the same (already-fixed) red.

🤖 Generated with Claude Code

just3ws and others added 3 commits July 7, 2026 16:41
actions/checkout@v4 defaults to fetch-depth: 1 (shallow). bin/generate_last_modified.rb
reads per-file dates via `git log -1 --format=%cI`; on a depth-1 clone that returns
the tip commit's date for every file, so CI regenerates _data/last_modified.yml with
uniform wrong dates and validate:generated_freshness fails (a full local clone passes).
Set fetch-depth: 0 in validate.yml + deploy.yml so CI has full history and regenerates
identical bytes. Unblocks the Pages deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The freshness gate failed in CI (not locally) on community_stories.yml and
interview_topics.yml. Root cause: both select interview IDs via
`sort_by(recorded_date).first(N)`, and recorded_date has ties (e.g. 7 interviews
on 2023-10-23). sort_by is not stable, so which IDs land in first(N) at a tie
boundary was not a pure function of the data — it drifted between the dev and CI
Ruby builds. Ruled out libyaml line-wrapping: interviews.yml has 145 wrapped
lines and never drifted, so wrapping is platform-stable here.

Add `id` as a total-order tiebreaker to both recorded_date sorts so committed
generated artifacts are deterministic regardless of sort implementation.

Also instrument validate_generated_freshness.rb to print the actual `git diff`
on failure, not just filenames — the missing diff is why this was CI-only
guesswork.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
With the freshness gate now passing, CI reached the Playwright smoke step and
failed at status 124 (~21s in) on the very first pwcli command. Root cause:
bin/smoke_playwright.sh caps each pwcli call at PWCLI_CMD_TIMEOUT (20s), but the
first call cold-fetches @playwright/cli and its browser on a fresh runner, which
exceeds 20s and aborts before any assertion runs.

Set PWCLI_CMD_TIMEOUT=180s for the CI smoke step only (local keeps the fast 20s
default) so the one-time cold fetch fits. Applied to both validate and deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@just3ws just3ws changed the title fix(ci): full-history checkout so last_modified freshness gate passes fix(ci): make generated artifacts deterministic; unblock freshness + smoke gates Jul 8, 2026
@just3ws
just3ws merged commit 4c33027 into master Jul 8, 2026
2 checks passed
@just3ws
just3ws deleted the ci/freshness-fetch-depth branch July 8, 2026 17:18
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