Skip to content

Add explicit readiness gate to live smoke suites (fix cold-start flake blocking prod Cloud Run deploy)#3770

Closed
anth-volk wants to merge 4 commits into
masterfrom
fix/staging-smoke-readiness-gate
Closed

Add explicit readiness gate to live smoke suites (fix cold-start flake blocking prod Cloud Run deploy)#3770
anth-volk wants to merge 4 commits into
masterfrom
fix/staging-smoke-readiness-gate

Conversation

@anth-volk

Copy link
Copy Markdown
Collaborator

Fixes #3769

Problem

The staging smoke suites run test_live_calculate.py first, and its first test posts a household calculate directly (api_client.post) with a 90s client timeout and no retry. Against the scale-to-zero staging Cloud Run service the candidate instance is cold, and the ~200s US tax-benefit-system import loses the race → httpx.ReadTimeout. The polling tests survive (_poll_live_endpoint retries on httpx.RequestError); the direct calculate does not.

Seen on release run 30014420432: 1 failed, 6 passed, failure = test_live_calculate_us_1. It cascades to skip the production Cloud Run deploy:

integration-tests-staging-cloud-run  FAILED
  └─ promote-cloud-run-staging          SKIPPED
      └─ ensure-production-model-...     SKIPPED
          └─ deploy-cloud-run-candidate  SKIPPED  (prod Cloud Run)

This is a cold-start timing race, not a product defect — but a staging flake silently blocking prod deploys is a migration hazard.

Fix

  • New tests/integration/test_live_readiness.py — a plain, visible test (not a conftest fixture, so the wait isn't buried and is easy to maintain) that polls /readiness-check until the deployed revision answers 200, with a deadline matching the startup-probe window (~480s) and retry-on-RequestError. It proves the deploy came up and warms the single instance.
  • Listed first in both staging smoke steps (App Engine + Cloud Run) — ordering is explicit in the workflow command, not reliant on collection order.

Why /readiness-check is the right signal: gunicorn workers import the app module (which builds the US system via country.py) before accepting connections, so a 200 from /readiness-check means the system is fully imported and calculate-ready.

Testing

.venv/bin/python -m pytest tests/integration/test_live_readiness.py -v
1 skipped   # skips locally without API_BASE_URL; runs in CI where it is set

Note: merging this creates a release whose staging smoke test now passes, which also unblocks the pending prod Cloud Run deploy of #3768.

🤖 Generated with Claude Code

anth-volk and others added 2 commits July 23, 2026 18:54
The staging smoke suites run test_live_calculate.py first, whose first test
posts a household calculate directly with a 90s client timeout and no retry.
Against the scale-to-zero staging Cloud Run service the candidate instance is
cold, and the ~200s US tax-benefit-system import loses the race to the timeout
(httpx.ReadTimeout). The failure cascades — the staging Cloud Run integration
test skips promote-cloud-run-staging, which skips the production Cloud Run
deploy.

Add tests/integration/test_live_readiness.py, listed first in both staging smoke
steps, which polls /readiness-check until the deployed revision answers 200.
This proves the deploy came up and warms the single instance so every following
test hits a ready service. Kept as a visible test (not a conftest fixture) so
the wait is obvious in the suite output and easy to maintain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test_push_workflow_tests_app_engine_and_cloud_run_staging_tracks pins the
exact staging smoke-test command; update the expected string to include the
readiness gate at the head, which also guards that it stays first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.03%. Comparing base (e287591) to head (66e3e21).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #3770   +/-   ##
=======================================
  Coverage   80.03%   80.03%           
=======================================
  Files          70       70           
  Lines        4342     4342           
  Branches      809      809           
=======================================
  Hits         3475     3475           
  Misses        648      648           
  Partials      219      219           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

anth-volk and others added 2 commits July 23, 2026 19:19
Replace the in-suite readiness test with a separate wait-for-cloud-run-staging-ready
job that polls /readiness-check (.github/scripts/wait_for_service_ready.sh) until
the freshly deployed candidate answers 200. integration-tests-staging-cloud-run
now needs that job, so the gate runs before all smoke tests and the first
calculate never races the cold-start import.

Its sole job is to determine readiness — no test assertions live in it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@anth-volk

Copy link
Copy Markdown
Collaborator Author

Superseded by the app-level fix (see the new PR): a startup warmup + honest /readiness-check. Polling /readiness-check from CI could never fix this because the endpoint returned 200 before the first-calculate machinery was compiled. Closing; the readiness-gate job is not needed.

@anth-volk anth-volk closed this Jul 23, 2026
@anth-volk
anth-volk deleted the fix/staging-smoke-readiness-gate branch July 23, 2026 22:05
anth-volk added a commit that referenced this pull request Jul 23, 2026
… boot budget

Follow-ups from reviewing this PR against the same checklist as #3770:

- openapi_spec.yaml: document the 503 /readiness-check now returns while warming
  (previously only 200 was documented).
- tests/unit/test_warmup.py: cheap structural guard that every warmup household's
  group-entity members reference a defined person (catches typos in the fast suite).
- tests/integration/test_warmup_household.py: slow guard (gated on
  RUN_WARMUP_INTEGRATION=1) that builds the real tax-benefit system and confirms
  each warmup household computes a finite result -- so a bad household can't
  silently no-op the warmup. Verified: passes in 191s for US.
- cloud-run-operations.md: state plainly that the warmup eats most of the headroom
  the 480s window buys (p90 ~371s -> ~420s), the far tail can still be killed and
  retried, and the lasting fix is prebuilding the system into the image.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

Staging Cloud Run smoke test intermittently fails on cold-start, skipping the prod Cloud Run deploy

1 participant