Skip to content

sync: geo.resolved reads THIS request's headers, on every backend #9

sync: geo.resolved reads THIS request's headers, on every backend

sync: geo.resolved reads THIS request's headers, on every backend #9

Workflow file for this run

name: CD
# Deploys llms.2plot.dev, then checks the live site.
#
# ============================ B3 TODO =====================================
# THE LIVE HALF OF THIS WORKFLOW IS DORMANT UNTIL A RENDER SERVICE EXISTS.
#
# Everything that touches a running host — the build-match wait and the whole
# `verify` job — is gated on the repository variable `SITE_URL`. While it is
# unset those steps skip with a notice and CD is effectively "CI plus an
# optional deploy trigger".
#
# This is not caution for its own sake. The template shipped
# SITE_URL=https://boilerplate.2plot.dev hard-coded, so an unguarded run here
# would poll ANOTHER SITE's /healthz for fifteen minutes waiting for a commit
# it will never serve, then battery it and report red — a fork failing CD on
# day one over a host it does not own. (Same class as the excalidraw finding:
# inherited workflows reference things that do not exist in the fork and
# deploy hosts that are not yours.) Pointing it at llms.2plot.dev today is no
# better: that domain still serves the OUTGOING site this migration replaces.
#
# TO UNGUARD, in two steps, no workflow edit either time:
# B3 (staging): set the repo variable SITE_URL to the service's
# https://<name>.onrender.com URL. The wait and the battery
# come alive against staging.
# B5 (cutover): change that same variable to https://llms.2plot.dev once
# the domain moves.
# ==========================================================================
#
# The deploy step POSTs to a Render deploy hook held in the
# RENDER_DEPLOY_HOOK_URL secret. Without that secret the step is skipped and
# the workflow goes straight to verification — useful when Render is already
# auto-deploying from GitHub on its own, and it means this repo doesn't fail
# CD on day one for a secret it was never given.
on:
push:
branches: [main]
workflow_dispatch:
inputs:
target_url:
description: Site to verify (skips the deploy when set to another host)
required: false
type: string
permissions:
contents: read
concurrency:
group: cd-production
cancel-in-progress: false
env:
PIP_DISABLE_PIP_VERSION_CHECK: "1"
# No hard-coded fallback, deliberately: an empty SITE_URL is the signal
# that no service exists yet, and every live step keys off it. A default
# here would silently re-point this workflow at somebody else's host.
SITE_URL: ${{ inputs.target_url || vars.SITE_URL }}
jobs:
test:
name: ci
uses: ./.github/workflows/ci.yml
deploy:
name: deploy to render
needs: [test]
runs-on: ubuntu-latest
# Long enough for the build-match wait below (up to 60 × 15s) and no
# longer. Without it the job inherits GitHub's six-hour default, which
# is how a platform that never comes back healthy holds the
# `cd-production` concurrency group all day.
timeout-minutes: 20
environment:
name: production
# B3 TODO: restore `url: ${{ inputs.target_url || vars.SITE_URL }}`
# when SITE_URL is set. Omitted while dormant rather than evaluated to
# an empty string — the deployment panel simply shows no link, and the
# first run in this repo's history does not hinge on how GitHub treats
# a blank environment URL.
outputs:
deployed: ${{ steps.hook.outputs.deployed }}
steps:
- name: Say plainly that the live half is dormant
if: env.SITE_URL == ''
run: |
echo "::notice::No Render service yet — SITE_URL repo variable is unset, so the build-match wait and the live batteries are skipped. Set SITE_URL to the .onrender.com URL at B3 to bring them up (see this workflow's header)."
- name: Trigger the Render deploy hook
id: hook
env:
HOOK: ${{ secrets.RENDER_DEPLOY_HOOK_URL }}
run: |
if [ -z "$HOOK" ]; then
echo "::notice::RENDER_DEPLOY_HOOK_URL is not set. Skipping the deploy trigger and verifying whatever is currently live."
echo "deployed=false" >> "$GITHUB_OUTPUT"
exit 0
fi
curl -fsS -X POST "$HOOK" > /dev/null
echo "deployed=true" >> "$GITHUB_OUTPUT"
- name: Wait for THIS build to serve traffic
# B3 TODO: skips while SITE_URL is unset — see the header. Without
# this guard the loop below polls a host this repo does not own for
# fifteen minutes and then fails the run.
if: env.SITE_URL != ''
# NOT gated on the hook step: with RENDER_DEPLOY_HOOK_URL unset,
# Render's own autoDeploy ships the commit minutes after the push —
# and the old shape skipped this wait and ran the battery three
# seconds later against the PREVIOUS release. That defect was
# invisible for the workflow's whole life (the old build always
# already passed the old battery) until a new surface made the race
# lose — found on muicharts, 2026-08-21, fleet-wide class.
run: |
# A bare 200 proves nothing about WHICH build answered: /healthz
# now reports the running instance's commit (RENDER_GIT_COMMIT),
# and this loop holds until it equals the SHA that triggered this
# run. Fallback: a build that predates the field gets the old
# sustained-health wait, with a warning naming what it can't tell
# — that fallback fires exactly once, on the deploy carrying the
# field for the first time.
want="${GITHUB_SHA}"
matched=0
ok=0
for _ in $(seq 1 60); do
body="$(curl -fsS "$SITE_URL/healthz" 2>/dev/null || true)"
if [ -n "$body" ]; then
build="$(printf '%s' "$body" | python3 -c 'import sys,json;print(json.load(sys.stdin).get("build",""))' 2>/dev/null || true)"
if [ "$build" = "$want" ]; then
matched=$((matched + 1))
[ "$matched" -ge 3 ] && break
elif [ -z "$build" ]; then
ok=$((ok + 1))
fi
fi
sleep 15
done
if [ "$matched" -ge 3 ]; then
echo "::notice::live /healthz reports build $want — verifying the artifact this run shipped."
elif [ "$ok" -ge 5 ]; then
echo "::warning::live /healthz predates the build field — cannot prove WHICH build is serving; verified sustained health only. This warning should appear exactly once."
else
echo "::error::$SITE_URL never served this run's build ($want) and never became reliably healthy"
exit 1
fi
verify:
name: verify the live site
needs: [deploy]
# B3 TODO: the whole job is dormant until SITE_URL names a real service.
# `vars.SITE_URL` rather than `env.SITE_URL` because job-level `if` is
# evaluated before the job's env context exists.
if: >-
always() && needs.deploy.result != 'cancelled'
&& (inputs.target_url || vars.SITE_URL) != ''
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
# The network battery first: it is the same script, with the same check
# names, that CI ran against the container this deploy shipped. A name
# that passed in CI and fails here isolates the fault to the deploy.
- name: Network smoke battery
run: python scripts/network_smoke.py --base-url "$SITE_URL"
# Then the satellite-specific checks the battery does not make: every
# canonical, every crawler body, and every peer llms.txt in the
# directory actually resolving.
- name: Smoke-test the deployment
run: python scripts/smoke_live.py "$SITE_URL"
- name: Report
if: failure()
run: |
echo "::error::Live verification failed for $SITE_URL. Every failure these check for is silent in production: a site identity that fell back to a framework default, a stale dash-improve-my-llms artifact, a canonical on the wrong host, a page serving the JavaScript stub, a missing network directory, and dead peer llms.txt links."