Skip to content

fix(ci): gate the production deploy on the API release, not just gateful - #2098

Merged
pikonha merged 7 commits into
mainfrom
fix/deploy-gate-upstream-commit
Aug 1, 2026
Merged

fix(ci): gate the production deploy on the API release, not just gateful#2098
pikonha merged 7 commits into
mainfrom
fix/deploy-gate-upstream-commit

Conversation

@pikonha

@pikonha pikonha commented Aug 1, 2026

Copy link
Copy Markdown
Member

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/json by merging the DAO APIs' specs live on every request (storeOpenApiSpecmergeUpstreamDocs), so gateful reporting the new commit says nothing about the schemas it will serve. The deploy gate only checked gateful:

14:33:54  Gateful ready after attempt 3: commit 773ae02b…   ← gate satisfied
14:34:23  @anticapture/client:codegen: cache miss, executing af63fa07…
14:34:34  ens-api (production): "server running"            ← 11s too late
14:36:15  Type error: Property 'scoresTotal' does not exist

The re-run then failed identically because turbo replayed the poisoned artifact — same commit, same hash:

14:55:13  @anticapture/client:codegen: cache hit, replaying logs af63fa07…

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/api reports its running commit on /health (RAILWAY_GIT_COMMIT_SHA, optional).
  • apps/gateful passes it through as upstreams.<dao>.commit. A probe body that isn't JSON, or carries no commit, is still status: "ok" — only the commit is lost.
  • scripts/wait-for-gateful.mjs gains staleUpstreams(); with REQUIRE_UPSTREAM_COMMIT=1 it 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.yaml sets that flag only when the push touches apps/api/** or infra/api/** — mirroring Railway's watchPatterns, 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#codegen is no longer cached. env is kept — turbo runs strict env mode, and dropping it would starve kubb of ANTICAPTURE_API_URL.

Verification

  • Replayed the real failure through the new detection step, run verbatim from the YAML: 67a6db5f…773ae02b (the actual push base — gateful was serving 67a6db5f) → changed=1, so it would have waited for ens-api. A non-API release → 0; zero-SHA and empty base → 0, no set -euo pipefail trip.
  • turbo run codegen --dry confirms cache {local: false, remote: false} with the env list intact.
  • api + gateful: full test suites, typecheck, lint. scripts/wait-for-gateful.test.mjs 8/8, gateful health route 10/10.

Notes

  • This hotfix's own deploy is still unguarded on the API side — the currently-running APIs don't report a commit yet, so the gate skips them. Harmless: no dashboard code reads the new field. Every release after it is covered.
  • Correction to an earlier note: packages/anticapture-client/generated/ is not committed (packages/anticapture-client/.gitignore ignores generated/*). It is produced fresh by every build from the live gateful spec, which is exactly why the gate has to be right.
  • The preview lane had none of this protection, and this PR's own preview build proved it by failing the same way production did (relayer mid-deploy at codegen time); tests.yaml now resolves the same per-upstream commit sets.
  • Considered and rejected: doing this via Railway healthchecks (no cross-service deploy ordering, and Railway can't gate a Vercel build), and requiring upstream commit equality in gateful's own healthcheck (gateful rebuilds on every push while the DAO APIs don't, so it would never go healthy).

🤖 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 commit on /health from RAILWAY_GIT_COMMIT_SHA. Gateful forwards that as upstreams.<dao>.commit. scripts/wait-for-gateful.mjs can require every upstream that reports a commit to match the expected SHA via REQUIRE_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/api or infra/api (mirroring Railway watch paths); unknown compare bases skip upstream waiting as before.

@anticapture/client#codegen is cache: false in turbo so a raced codegen run is not replayed on retries of the same commit.

Reviewed by Cursor Bugbot for commit 002b33c. Configure here.

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]>
@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
anticapture-storybook Ready Ready Preview Aug 1, 2026 4:45pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
anticapture Ignored Ignored Aug 1, 2026 4:45pm

Request Review

@railway-app

railway-app Bot commented Aug 1, 2026

Copy link
Copy Markdown

🚅 Deployed to the anticapture-pr-2098 environment in anticapture-infra

Service Status Web Updated (UTC)
gitcoin-indexer-offchain ✅ Success (View Logs) Aug 1, 2026 at 4:50 pm
ens-indexer-offchain ✅ Success (View Logs) Aug 1, 2026 at 4:50 pm
shutter-indexer-offchain ✅ Success (View Logs) Aug 1, 2026 at 4:50 pm
uniswap-indexer-offchain ✅ Success (View Logs) Aug 1, 2026 at 4:50 pm
compound-indexer-offchain ✅ Success (View Logs) Aug 1, 2026 at 4:49 pm
authful ✅ Success (View Logs) Web Aug 1, 2026 at 4:44 pm
gateful ✅ Success (View Logs) Web Aug 1, 2026 at 4:44 pm
prometheus ✅ Success (View Logs) Aug 1, 2026 at 4:44 pm
tempo ✅ Success (View Logs) Aug 1, 2026 at 4:44 pm
grafana ✅ Success (View Logs) Web Aug 1, 2026 at 4:44 pm
loki ✅ Success (View Logs) Aug 1, 2026 at 4:43 pm
alertmanager ✅ Success (View Logs) Web Aug 1, 2026 at 4:43 pm
otelcol ✅ Success (View Logs) Aug 1, 2026 at 4:43 pm
address-enrichment ✅ Success (View Logs) Web Aug 1, 2026 at 4:11 pm
ens-relayer ✅ Success (View Logs) Aug 1, 2026 at 4:10 pm
mcp ✅ Success (View Logs) Web Aug 1, 2026 at 3:44 pm
docs ✅ Success (View Logs) Web Aug 1, 2026 at 3:44 pm
obol-api ✅ Success (View Logs) Aug 1, 2026 at 3:36 pm
aave-api ✅ Success (View Logs) Aug 1, 2026 at 3:36 pm
compound-api ✅ Success (View Logs) Aug 1, 2026 at 3:36 pm
fluid-indexer ✅ Success (View Logs) Aug 1, 2026 at 3:36 pm
lil-nouns-indexer ✅ Success (View Logs) Aug 1, 2026 at 3:36 pm
tornado-indexer ✅ Success (View Logs) Aug 1, 2026 at 3:36 pm
uniswap-api ✅ Success (View Logs) Aug 1, 2026 at 3:36 pm
gitcoin-api ✅ Success (View Logs) Aug 1, 2026 at 3:36 pm
shutter-api ✅ Success (View Logs) Aug 1, 2026 at 3:36 pm
nouns-indexer ✅ Success (View Logs) Aug 1, 2026 at 3:36 pm
fluid-api ✅ Success (View Logs) Aug 1, 2026 at 3:36 pm
gitcoin-indexer ✅ Success (View Logs) Aug 1, 2026 at 3:36 pm
scroll-indexer ✅ Success (View Logs) Aug 1, 2026 at 3:36 pm
aave-indexer ✅ Success (View Logs) Aug 1, 2026 at 3:36 pm
ens-indexer ✅ Success (View Logs) Aug 1, 2026 at 3:35 pm
shutter-indexer ✅ Success (View Logs) Aug 1, 2026 at 3:35 pm
tornado-api ✅ Success (View Logs) Aug 1, 2026 at 3:35 pm
lil-nouns-api ✅ Success (View Logs) Aug 1, 2026 at 3:35 pm
ens-api ✅ Success (View Logs) Aug 1, 2026 at 3:35 pm
uniswap-indexer ✅ Success (View Logs) Aug 1, 2026 at 3:35 pm
obol-indexer ✅ Success (View Logs) Aug 1, 2026 at 3:35 pm
nouns-api ✅ Success (View Logs) Aug 1, 2026 at 3:35 pm
compound-indexer ✅ Success (View Logs) Aug 1, 2026 at 3:35 pm
erpc ✅ Success (View Logs) Web Aug 1, 2026 at 3:35 pm
nodeful ✅ Success (View Logs) Aug 1, 2026 at 3:35 pm
scroll-api ✅ Success (View Logs) Aug 1, 2026 at 3:34 pm
user-api ✅ Success (View Logs) Web Aug 1, 2026 at 3:33 pm

@railway-app
railway-app Bot temporarily deployed to anticapture-infra / anticapture-pr-2098 August 1, 2026 15:32 Destroyed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/wait-for-gateful.mjs
Comment thread .github/workflows/deploy.yaml Outdated
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

🔍 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]>
@railway-app
railway-app Bot temporarily deployed to anticapture-infra / anticapture-pr-2098 August 1, 2026 15:43 Destroyed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread .github/workflows/deploy.yaml Outdated
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]>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread scripts/wait-for-gateful.mjs Outdated
…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]>
@railway-app
railway-app Bot temporarily deployed to anticapture-infra / anticapture-pr-2098 August 1, 2026 16:06 Destroyed
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]>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread turbo.json
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]>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread turbo.json Outdated
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]>
@railway-app
railway-app Bot temporarily deployed to anticapture-infra / anticapture-pr-2098 August 1, 2026 16:43 Destroyed
@pikonha
pikonha merged commit 059370d into main Aug 1, 2026
60 checks passed
@pikonha
pikonha deleted the fix/deploy-gate-upstream-commit branch August 1, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant