fix(ci): gate the production deploy on the API release, not just gateful - #2098
Conversation
Gateful merges the DAO APIs' OpenAPI specs into /docs/json on every request, so waiting for gateful's own commit said nothing about the schemas it would serve. On #2093 gateful reported the new commit at 14:33:54, the dashboard's codegen read the spec at 14:34:23, and ens-api only came up at 14:34:34 — the build typechecked against the previous contract and failed on scoresTotal. The DAO API now reports its running commit on /health, gateful surfaces it as upstreams.<dao>.commit, and wait-for-gateful waits for every upstream that reports one. That is enabled only when the push touches the paths Railway watches to rebuild those APIs, so releases that leave them alone are not blocked waiting for a rebuild that will never happen. Also stop caching @anticapture/client#codegen: its real input is the live gateful spec, which no hash can see, so the raced output was replayed on every retry of that commit and no re-run could ever fix it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
|
🚅 Deployed to the anticapture-pr-2098 environment in anticapture-infra
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 002b33ca39
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
🔍 Vercel preview: https://anticapture-elyfvwh27-ful.vercel.app |
…ad compare Two review findings on the gate. A DAO API that reports no commit was being skipped, which is exactly the state of every API during this release's own rollout — the field ships with it. The gate would have passed the moment gateful updated, reproducing the race. DAO APIs now have to report the expected commit; gateful labels each upstream with a `kind` so relayers, address enrichment and authful stay exempt. A failing `gh api` inside an `if` was indistinguishable from "no API change", so a rate limit or outage would silently drop the gate on the releases that need it most. It now runs as its own command, where set -e fails the step. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5f90b67666
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
A third review finding: with `cancel-in-progress`, a non-API push landing while an API push is still deploying cancels the earlier waiter, and asking "did THIS push change the API" answers no — so the gate stood down while the APIs were still serving the pre-release spec. The workflow now resolves the last commit that touched the paths Railway watches for the DAO APIs, and passes that commit plus every commit after it. A superseding push resolves the same API commit and keeps waiting for it, and accepting "or newer" also covers Railway stamping a service with the head of a multi-commit push rather than the commit that changed it. This drops the REQUIRE_UPSTREAM_COMMIT flag and the compare API call along with it — git already knows, so there is no request left to fail. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6e33d1ea8b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…he DAO APIs A fourth review finding, and a factual correction to a comment I wrote: the relayer and address enrichment also contribute paths and schemas to /docs/json via mergeUpstreamDocs, so a release changing either of them could reproduce the same stale-spec race the DAO API gate was closing. Both now report their commit on /health, and the expected commits are resolved per upstream kind from that service's own Railway watch paths — a relayer release waits for the relayer, not for the APIs. Authful stays exempt: its spec is not merged, so it only has to be reachable. Requiring a commit cannot deadlock these two either: teaching a service to report its commit necessarily touches its own watched paths, so the release that adds the field is also the release that rebuilds it. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This PR's own preview build failed exactly the way production did: codegen ran at 16:10:45 while the preview relayer, redeployed at 16:07 because this branch touches apps/relayer, was still coming up. Gateful served a spec whose relay paths had no matching schemas, so the generated sdk.ts imported types models.ts never got and the SDK build failed on them. Re-running with no code change passed, which is the race admitting what it is. The preview waiter only ever checked gateful's own commit, so it had none of the protection deploy.yaml just gained. It now resolves the same per-upstream commit sets from the PR head. Untrusted PRs point at shared dev gateful and already leave the expected commit unset; the upstream check rides along with it and stays off for them. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9c26667345
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Re-running codegen on a retry was pointless while `@anticapture/client#build` still hashed the same: `generated/**` is gitignored, so it never entered that task's input set, and turbo would restore the `dist` compiled from the stale spec over the freshly generated one. The dashboard build hashes the client build in turn, so it inherited the same blindness. Listing `generated/**` explicitly makes the spec visible to both hashes while keeping the cache useful when the spec really is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 78a5329670
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
78a5329 declared `generated/**` as an input to `@anticapture/client#build`, which does not work: turbo hashes inputs before the run, and the directory is gitignored, so on a fresh checkout the glob matches nothing at hash time. The retry hashed identically to the poisoned run and restored its `dist`. Reproduced in isolation with turbo 2.8.16 — a cache:false codegen writing a gitignored dir, a cached build consuming it, the same hash across both runs and the stale output replayed on top of fresh generated files. tsup runs with `dts: true`, so the spec fetched at runtime is compiled into `dist` and no hash can see it — the same reason codegen is uncached. The build is ~6s, so this is cheap. `@anticapture/dashboard#build` keeps its cache: turbo only caches successes, and it is the task that fails in this mode. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Why
The production dashboard build for #2093 failed on
Property 'scoresTotal' does not exist on type 'OffchainProposal'— twice, including a manual re-run. Neither failure was a code bug.Gateful builds
/docs/jsonby merging the DAO APIs' specs live on every request (storeOpenApiSpec→mergeUpstreamDocs), so gateful reporting the new commit says nothing about the schemas it will serve. The deploy gate only checked gateful:The re-run then failed identically because turbo replayed the poisoned artifact — same commit, same hash:
codegen's real input is a live URL no hash can see, so a raced run is cached under that SHA and no retry can ever refetch.What changed
apps/apireports its running commit on/health(RAILWAY_GIT_COMMIT_SHA, optional).apps/gatefulpasses it through asupstreams.<dao>.commit. A probe body that isn't JSON, or carries no commit, is stillstatus: "ok"— only the commit is lost.scripts/wait-for-gateful.mjsgainsstaleUpstreams(); withREQUIRE_UPSTREAM_COMMIT=1it waits for every upstream that reports a commit. Upstreams that don't (relayers, address-enrichment, authful, any API predating the field) are skipped, so the gate can't deadlock on services a release never rebuilds..github/workflows/deploy.yamlsets that flag only when the push touchesapps/api/**orinfra/api/**— mirroring Railway'swatchPatterns, i.e. "will these APIs actually redeploy". Unknown base (new branch / force push) → off, which is the behaviour the gate had before.turbo.json:@anticapture/client#codegenis no longer cached.envis kept — turbo runs strict env mode, and dropping it would starve kubb ofANTICAPTURE_API_URL.Verification
67a6db5f…773ae02b(the actual push base — gateful was serving67a6db5f) →changed=1, so it would have waited forens-api. A non-API release →0; zero-SHA and empty base →0, noset -euo pipefailtrip.turbo run codegen --dryconfirmscache {local: false, remote: false}with the env list intact.scripts/wait-for-gateful.test.mjs8/8, gateful health route 10/10.Notes
packages/anticapture-client/generated/is not committed (packages/anticapture-client/.gitignoreignoresgenerated/*). It is produced fresh by every build from the live gateful spec, which is exactly why the gate has to be right.tests.yamlnow resolves the same per-upstream commit sets.🤖 Generated with Claude Code
Note
Medium Risk
Changes CI deploy gating and live codegen behavior; mis-tuned path detection could delay or skip waiting, but health/commit fields are additive and optional.
Overview
Fixes production dashboard builds that ran client codegen against an outdated merged OpenAPI spec because gateful can be on the new commit while slower DAO APIs still serve the previous schema.
DAO APIs now include optional
commiton/healthfromRAILWAY_GIT_COMMIT_SHA. Gateful forwards that asupstreams.<dao>.commit.scripts/wait-for-gateful.mjscan require every upstream that reports a commit to match the expected SHA viaREQUIRE_UPSTREAM_COMMIT=1; upstreams without a commit are ignored so relayers and older APIs do not block deploys.The deploy workflow sets that flag only when the push changes
apps/apiorinfra/api(mirroring Railway watch paths); unknown compare bases skip upstream waiting as before.@anticapture/client#codegeniscache: falsein turbo so a raced codegen run is not replayed on retries of the same commit.Reviewed by Cursor Bugbot for commit 002b33c. Configure here.