feat/ci-speed
needs attentionviewing older commitf7dbbf7 · fullpre-PRreviewed 2026-08-07 02:01 UTC0H · 5M · 7L · 4I- Purpose
- Cut PR gate latency — the dominant cost was 287s of Vercel build polling inside a job that runs 21s of actual tests
- Goal
- Reduce warm-path E2E gate time from ~6 min to ~1.5 min by triggering on deployment_status (Vercel READY) instead of pull_request (races the build); add Turbo Remote Cache to cut typecheck/lint from ~215s cold to ~seconds on cache hit
- Sub-goals
- SG-1: Switch e2e.yml trigger to deployment_status with correct gate logic (PR resolution, supersession, branch-name filter)
- SG-2: Add TURBO_TOKEN + TURBO_TEAM to pr-checks.yml validate job for Vercel remote cache
- SG-3: Update e2e-testing.md to document the new trigger mechanics and required-check posture
- SG-4: Add CI/CD diagnosis report documenting measured bottlenecks and recommended sequence
- What
- e2e.yml trigger changed from pull_request to deployment_status; gate step rewritten to resolve PR/branch from SHA via GitHub API; concurrency group keyed on SHA; pr-checks.yml validate job gets TURBO_TOKEN/TURBO_TEAM env; e2e-testing.md updated; docs/ci-cd-diagnosis.html added
- Why
- 81% of the E2E job (287s of 354s) was curl-polling for Vercel to finish building. Starting from a READY deployment eliminates that wait. Turbo Remote Cache eliminates redundant typecheck/lint recomputation across CI runs.
- Areas
- .github/workflows/e2e.yml+111−55.github/workflows/pr-checks.yml+12−0.claude/rules/e2e-testing.md+29−19docs/ci-cd-diagnosis.html+127−0
- Blast
- 4 files, +279/−74 lines; CI/CD infrastructure only — no domain code, no migrations, no API surface changes
Findings · 16
correctness3
Gate 'branch' output missing when PLATFORM_PREVIEW_URL override + non-matching branch name
.github/workflows/e2e.yml
When PLATFORM_PREVIEW_URL is set as a repo var but HEAD_REF does not match feat|fix|hotfix|feature, the gate emits run=true but never emits branch=$HEAD_REF. The 'Resolve preview URLs' step then does SLUG=$(echo "${{ steps.gate.outputs.branch }}") with an empty value, producing a malformed alias URL like https://batu-codebase-platform-git--batu-energy.vercel.app (double dash). The E2E smoke would target a non-existent host. Fix: also emit `echo "branch=$HEAD_REF" >> "$GITHUB_OUTPUT"` in that else-with-override branch.
Duplicate lines in workflow comment block
.github/workflows/e2e.yml
The diff shows the lines '- The branch's Supabase preview DB is seeded by preview-provision.yml, so the' and 'seeded user (default diego@batuenergy.com / BatuPreview2024!) can log in.' each appearing twice in the comment header — a copy-paste merge artifact. Does not affect runtime but clutters the canonical source.
Cross-push supersession relies on GitHub API currency
.github/workflows/e2e.yml
For rapid successive pushes (different SHAs), the concurrency group (keyed on SHA) does NOT cancel — the gate's HEAD_SHA != SHA check is the only guard. If the GitHub commits API is stale (edge case), an older SHA's run could slip through and test a superseded commit. Low probability but worth noting alongside the concurrency comment.
security3
VERCEL_TOKEN reused as TURBO_TOKEN — overprivileged cache credential
.github/workflows/pr-checks.yml
TURBO_TOKEN is set to ${{ secrets.VERCEL_TOKEN }}, which carries deploy, env-var sync, and project promotion privileges across all Vercel projects. A Turbo cache token only needs remote-cache read/write scope. Using the deploy token means a cache-poisoning incident or a token leak from a build log also exposes full Vercel deploy access. Preferred path: generate a scoped token via Vercel dashboard → team settings → Remote Cache, store as TURBO_TOKEN secret, and reference that. The comment documents the intentional reuse but 'avoids a new secret' is a weak justification for carrying deploy authority into the validate job.
Verify Vercel is NOT configured to build fork PRs
.github/workflows/e2e.yml
deployment_status fires for any deployment attached to the repo. If Vercel is configured to build fork PRs, a fork actor could cause a deployment_status success event and the workflow would check out and execute that SHA's workflow code. Confirm batu-codebase-platform does not build fork PRs in the Vercel project settings.
Branch name written to GITHUB_OUTPUT without value quoting
.github/workflows/e2e.yml
echo "branch=$HEAD_REF" >> "$GITHUB_OUTPUT" does not quote the value side. A branch name containing a newline or GITHUB_OUTPUT delimiter could corrupt the output file. GitHub rejects most pathological names, so this is very low likelihood, but using the heredoc form (echo "branch<<EOF\n$HEAD_REF\nEOF") is the hardened pattern.
conventions2
loops.md still says 'e2e testing' is a required check
.claude/rules/loops.md
loops.md Safety model § point 2 references 'the `e2e testing` required check'. The diff updates e2e-testing.md to document that the check is currently NOT required, but loops.md was not updated. The two rule files are now inconsistent. A reader of loops.md will believe enforcement is in place when it is not.
migration-lint absent from summary.needs (pre-existing)
.github/workflows/pr-checks.yml
The summary job's needs list omits migration-lint, so a failing migration-lint run does not block All Checks Passed. Pre-existing issue not introduced by this PR; will need fixing when MON-48 promotes migration-lint to blocking.
tests4
deployment_status trigger is inert on this branch — the new code path ships unvalidated
.github/workflows/e2e.yml
GitHub only fires deployment_status for the workflow file on the default branch (main). The branch copy of e2e.yml is completely inert. This means the gate step logic (jq filter, SHA comparison, branch-name regex, output wiring) cannot be exercised on this PR itself. The functional bug in finding #1 is an example of exactly what this blind spot hides. The workflow header correctly documents this limitation, but it means the change ships with zero live CI runs validating the new code path. Manual testing via dispatch or a post-merge monitoring window is recommended.
Non-preview branches produce no check run — latent re-enablement hazard
.github/workflows/e2e.yml
Under pull_request, docs/chore branches got a skip→pass run that satisfied a required-check gate. Under deployment_status they produce NO run. The workflow header and e2e-testing.md document this correctly and note the check is NOT currently required. The risk: if a future contributor re-enables 'e2e testing' as required without first reading the caveat and adding a pull_request pass-through, every docs/chore PR blocks forever. Adding a commented-out pass-through skeleton would make the safe re-enablement path structurally obvious.
180s alias-flip deadline may be too tight for Vercel propagation lag
.github/workflows/e2e.yml
The deployment_status event fires when the build finishes, but Vercel's branch alias update is a separate async step. Under load, alias propagation can lag by minutes. The old 600s budget absorbed this; 180s may not. If the alias flip takes >180s the readiness check fails and E2E aborts, producing a flaky gate. Monitoring actual alias-flip latency on a few post-merge runs before accepting this as the new budget is advisable.
Gate skip reports as green 'e2e testing' check
.github/workflows/e2e.yml
When the gate skips (no PR, superseded, non-preview), the job exits 0 and GitHub shows a green 'e2e testing' check. This could be misread as 'tests ran and passed'. Intentional design given the check is not currently required, but worth noting.
improvement4
Gate picks first open PR arbitrarily when a SHA has multiple open PRs to main
.github/workflows/e2e.yml
The jq expression `[.[] | select(.state=="open" and .base.ref=="main")][0] // empty` silently picks the first result when a commit SHA appears on more than one open PR targeting main (cherry-pick branches, rebase collisions). Sort order is not a documented API guarantee. A safer policy: if `length > 1`, emit a notice and skip (or pick by `.updated_at` descending). Silent first-pick makes the behavior nondeterministic and hard to debug.
docs/ci-cd-diagnosis.html is a transient diagnostic committed to source
docs/ci-cd-diagnosis.html
The file is a point-in-time measurement snapshot (timings, waterfall, findings). It goes stale the moment the pipeline improves and will require a follow-up cleanup commit. Diagnostic artifacts belong in a PR description, GitHub wiki, or gist — not in docs/ alongside forward-looking strategy docs.
Slug derivation may drift from preview-provision.yml transform
.github/workflows/e2e.yml
The slug is derived with `sed 's/[/]/-/g' | tr '[:upper:]' '[:lower:]'`. If preview-provision.yml uses a slightly different transform for edge-case branch names, the constructed alias URL will 404 and manifest as a misleading readiness timeout rather than a clear URL mismatch error. A shared composite action or a canonical slug script referenced by both workflows would eliminate this latent drift.
wait-on 5-minute timeout for web is too generous post-trigger-change
.github/workflows/e2e.yml
Under deployment_status the platform deployment is already READY. The web project deploys in parallel and typically finishes first. The 300 000 ms (5 min) wait-on is a residual of the old pull_request timing where web might still be building. A 90–120s timeout would fail faster on a genuine web failure and reduce the job's worst-case tail.