ci(deploy): gate API deploy on affected paths - #2711
Conversation
Co-authored-by: polylane[bot] <277585245+polylane[bot]@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
The PR only touches The test workflow runs turbo-filtered tests per package. Let me see the actual test commands and the rest of the workflow, then reproduce the failures locally. The PR touches only The api leg failed (run 34793719478), and I need the services leg's id and the actual failure text. Let me grep the spill file and fetch annotations plus job logs. The spill is pretty-printed JSON. Let me parse it with python to get the run ids and conclusions. The spill file was truncated (65KB shown, 65,841 bytes total, ends mid-JSON). Let me fetch the check runs again with a tight projection directly. The failing legs are 🧪 api and 🧪 services (plus the aggregate 🧪 Tests). The PR only touches The annotations confirm The job logs are saved to a spill file. Let me search it for the actual failure lines. The grep matched too much (spill got re-spilled). Let me look at the structure of the file first. The log is JSON-escaped and the spill was truncated. Let me fetch each log and extract just the failing test section inside the sandbox. The real failures are in two test files, both unrelated to my deploy.yml change: The failures are in test files my PR never touched ( The base commit a6887e1 passed Tests on main (run 34777866818 success), and the PR branch is base + one YAML file. Let me read the full failure stacks to confirm the exact assertions before deciding. The failure picture is now clear from the CI logs I already have: No deno binary and no The failing assertions are The helpers Retained head: the failing CI runs at 00:46Z for api (statusPage.utils.test.ts:530/547, successCard undefined) and services (get-daily-summary.test.ts:187 length 1 vs 2). I read the code path: createStatusData day 0 = "today", events span [00:00, 01:00) UTC, setDataByType duration uses getAdjustedTotalMinutesInDay(date, maintenances) = getTotalMinutesInDay(date) minus maintenance; getTotalMinutesInDay lines 120-126: if date is "today", returns minutesElapsed since midnight (00:00 at 00:46Z), else 24*60. Line 115-119 elided: getTotalMinutesInDay(date) returns minutesElapsed when date.getTime() === startOfDay at midnight? Let me re-read that top part, since the 00:00Z edge is the crux: successMinutes = max(1440 - 60 - 0, 0) = 1380 normally, but at 00:46Z with the minute-elapsed path it could be ~46 - 60 → clamped 0 → null. That's the mechanism, and the same "current day" logic explains services get-daily-summary (2 expected buckets: completed day + in-progress day; at 00:46Z the in-progress day has only elapsed minutes, or the query filters to days with data). I need: (a) the exact getTotalMinutesInDay head (lines 100-120), (b) the services get-daily-summary day-window logic, to state the mechanism precisely, then decide: fix or no-action. The PR is a YAML-only change; both failures are time-of-day-sensitive tests that passed at 19:30Z on the identical code. My change cannot have caused them. The question is whether a fix belongs in this PR (it does not — the PR is the deploy gate), so I should end with the no-action note unless the failures are flaky in a way that needs a code change unrelated to my diff. Let me re-read the two code sections. |
Fixes: Content-only commit redeploys API + all Vercel apps; web build flaky
Every push to main currently redeploys the production API, even a commit that only edits marketing copy or the README. That means a content typo can churn the API fleet and its healthchecks for no reason, and every merge multiplies deploy risk across four production surfaces. This change adds a paths filter to the Fly Deploy workflow so the API deploys only when
apps/serveror one of the packages it depends on changes, mirroring the gate the workflows app already uses.What caused this
Affected:
int_01b1b9dbe001vd8itqqr5fpc· severity mediumWhat changed
.github/workflows/deploy.yml: added anon.push.pathsfilter so the API deploy runs only whenapps/server/**, a dependency package underpackages/**(analytics, assertions, db, emails, error, importers, locales, notifications, proto, regions, services, subscriptions, theme-store, tinybird, tracker, tsconfig, upstash, utils), the workflow file itself, or the pnpm manifests change.Why it's safe
apps/server/Dockerfilebind-mount list andpackage.jsondependencies); every mounted/imported package is covered, pluspnpm-lock.yaml/pnpm-workspace.yamland the workflow file itself.deploy-workflows.ymlhas run in production since before this incident; no other workflow semantics are touched.ai,api,header-analysis,icons,react,status-fetcher,test-utils,ui) are deliberately absent, so the filter does not reintroduce the noise it removes.Validation
YAML.load_filesucceeds; the only quirk is YAML 1.1 treatingonas boolean, which GitHub's Actions parser handles and every workflow in this repo shares)..github/workflows/deploy-workflows.yml— same quoting, samebranches/pathslayout, same indentation.apps/server/Dockerfilebind-mount list andapps/server/package.jsondependencies — every package the API image mounts or imports is in the filter.pnpm verify(repo lint/typecheck): the change is a workflow YAML only; no TS/JS or package code changed, so oxfmt/oxlint/deno check have nothing to evaluate in this diff.Root cause and scoping notes
Root cause
.github/workflows/deploy.ymlfires onpush: branches: [main]with nopathsfilter, so every main push runsflyctl deployfor the API. The siblingdeploy-workflows.ymlalready gates its app on paths; the API workflow never got the same gate.openstatus-webhascommandForIgnoringBuildStep: "npx turbo-ignore"in the Vercel console; dashboard and status-page have none, and no repo file declares these settings. Recorded as a follow-up on the issue timeline.Causal chain
Detection outcome
The signal "every commit, even a marketing-copy-only change, redeploys the API server" stops firing once this merges: the Fly Deploy workflow is now gated on
paths, so a content-only commit produces no API release (the 19:30:42Z release 2210 class of redeploy cannot occur for such a commit). The same commit's rebuilds ofopenstatus-dashboardandopenstatus-status-pageare a Vercel-console setting (no ignore-build-step command configured), tracked as a follow-up on the issue timeline rather than fixed here.Fix chosen
Chosen: cause (removes the mechanism that produces the failure): Add an on.push.paths filter to .github/workflows/deploy.yml so the API deploys only when apps/server, a dependency package, the workflow, or the pnpm manifests change.
Considered and not chosen:
Outcome after fix
Replaying the 2026-09-13 scenario with the change applied: a push of a6887e1 (README +
apps/web/**only) matches none of thepathsentries, so GitHub skipsFly Deployentirely — no openstatus-api release is created, no fleet churn, no healthcheck reset. A push that touchesapps/server/**,packages/db/**, orpnpm-lock.yamlmatches the filter and the workflow runs byte-for-byte as it does today. The status-page and dashboard production builds on content-only commits will keep firing until the Vercel console setting is applied (recorded on the issue timeline); the API half of the finding is removed by this change.1 file changed (+23/-0)
.github/workflows/deploy.yml: modified, +23/-0Repository conventions present in the repository: AGENTS.md, CLAUDE.md, apps/checker/AGENTS.md, apps/dashboard/AGENTS.md, apps/server/AGENTS.md, apps/status-page/AGENTS.md, apps/web/AGENTS.md, apps/workflows/AGENTS.md, packages/services/AGENTS.md, packages/ui/AGENTS.md.
Repository lint:
pnpm run lint(declared in package.json) could not run in the sandbox because its tool is not installed there; run it before merging.Generated by Polylane.