From 6316b05bbb38d4d942c520faa9ca3a676e0a6ba3 Mon Sep 17 00:00:00 2001 From: Khaliq Date: Thu, 8 Oct 2026 00:40:03 -0700 Subject: [PATCH 1/3] ci(router): accept a later successful web deploy that includes this commit A newer push supersedes a queued web deploy in its concurrency group, so a router run waiting for its own commit's web deploy saw it cancelled and gave up (#162's router never shipped). Ship once any successful web deploy of this commit or a descendant exists; still refuse if this commit's own web deploy failed. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/deploy-router.yml | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy-router.yml b/.github/workflows/deploy-router.yml index 3ed90bc9..c4790717 100644 --- a/.github/workflows/deploy-router.yml +++ b/.github/workflows/deploy-router.yml @@ -26,25 +26,36 @@ jobs: # The router rewrites public paths onto web pages (arelay.to/ -> # /u/), so a router that ships before the web deploy of the same # commit routes to pages that don't exist yet. That took - # arelay.to/agent-relay down for ~10 minutes when #150 merged. Wait for - # this commit's web deploy and refuse to ship if it failed. - - name: Wait for the web deploy of this commit + # arelay.to/agent-relay down for ~10 minutes when #150 merged. Wait until + # a successful web deploy includes this commit: its own, or a later one + # (a newer push supersedes a queued web deploy in its concurrency group, + # and the later deploy contains this commit's pages too). Refuse to ship + # if this commit's own web deploy failed. + - name: Wait for a web deploy that includes this commit if: github.event_name == 'push' env: GH_TOKEN: ${{ github.token }} run: | for _ in $(seq 1 240); do - state=$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/deploy.yml/runs?head_sha=${GITHUB_SHA}&event=push&per_page=1" \ + own=$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/deploy.yml/runs?head_sha=${GITHUB_SHA}&event=push&per_page=1" \ --jq '.workflow_runs[0] | if . == null then "missing" else "\(.status) \(.conclusion // "")" end' \ || echo "lookup failed (will retry)") - echo "web deploy for ${GITHUB_SHA::9}: ${state}" - case "$state" in + echo "web deploy for ${GITHUB_SHA::9}: ${own}" + case "$own" in "completed success") exit 0 ;; - completed*) echo "::error::Deploy Web (Cloudflare) did not succeed for this commit; not deploying the router."; exit 1 ;; + "completed failure"|"completed timed_out"|"completed startup_failure") + echo "::error::Deploy Web (Cloudflare) failed for this commit; not deploying the router."; exit 1 ;; esac + latest=$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/deploy.yml/runs?branch=main&event=push&status=success&per_page=1" \ + --jq '.workflow_runs[0].head_sha // ""' || echo "") + if [ -n "$latest" ] && [ "$latest" != "$GITHUB_SHA" ]; then + rel=$(gh api "repos/${GITHUB_REPOSITORY}/compare/${GITHUB_SHA}...${latest}" --jq '.status' || echo "") + echo "latest successful web deploy ${latest::9} is ${rel:-unknown} relative to this commit" + case "$rel" in ahead|identical) exit 0 ;; esac + fi sleep 30 done - echo "::error::Timed out after 2 hours waiting for Deploy Web (Cloudflare) for this commit. Re-run this workflow once it finishes." + echo "::error::Timed out after 2 hours waiting for a web deploy that includes this commit. Re-run this workflow once one succeeds." exit 1 - name: Checkout code From 0893a669460772a9c3b2329347dade6a55e129a4 Mon Sep 17 00:00:00 2001 From: Khaliq Date: Thu, 8 Oct 2026 00:52:14 -0700 Subject: [PATCH 2/3] ci(router): deploy the accepted revision; a lookup error never releases - Check out the revision whose web deploy succeeded (this commit or the later one that contains it), so router and pages always match. - Only look for a later deploy after this commit's own deploy was read and is not a failure; a transient lookup error just retries. - An identical SHA is accepted through the same path. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/deploy-router.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-router.yml b/.github/workflows/deploy-router.yml index c4790717..9e8b9386 100644 --- a/.github/workflows/deploy-router.yml +++ b/.github/workflows/deploy-router.yml @@ -32,6 +32,7 @@ jobs: # and the later deploy contains this commit's pages too). Refuse to ship # if this commit's own web deploy failed. - name: Wait for a web deploy that includes this commit + id: wait if: github.event_name == 'push' env: GH_TOKEN: ${{ github.token }} @@ -39,19 +40,23 @@ jobs: for _ in $(seq 1 240); do own=$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/deploy.yml/runs?head_sha=${GITHUB_SHA}&event=push&per_page=1" \ --jq '.workflow_runs[0] | if . == null then "missing" else "\(.status) \(.conclusion // "")" end' \ - || echo "lookup failed (will retry)") + || echo "lookup-failed") echo "web deploy for ${GITHUB_SHA::9}: ${own}" case "$own" in - "completed success") exit 0 ;; + "completed success") echo "sha=${GITHUB_SHA}" >> "$GITHUB_OUTPUT"; exit 0 ;; "completed failure"|"completed timed_out"|"completed startup_failure") echo "::error::Deploy Web (Cloudflare) failed for this commit; not deploying the router."; exit 1 ;; + lookup-failed) sleep 30; continue ;; esac + # Own deploy is missing, queued, running or cancelled (superseded): + # accept the newest successful web deploy if it contains this commit, + # and deploy the router at THAT revision so router and pages match. latest=$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/deploy.yml/runs?branch=main&event=push&status=success&per_page=1" \ --jq '.workflow_runs[0].head_sha // ""' || echo "") - if [ -n "$latest" ] && [ "$latest" != "$GITHUB_SHA" ]; then + if [ -n "$latest" ]; then rel=$(gh api "repos/${GITHUB_REPOSITORY}/compare/${GITHUB_SHA}...${latest}" --jq '.status' || echo "") echo "latest successful web deploy ${latest::9} is ${rel:-unknown} relative to this commit" - case "$rel" in ahead|identical) exit 0 ;; esac + case "$rel" in ahead|identical) echo "sha=${latest}" >> "$GITHUB_OUTPUT"; exit 0 ;; esac fi sleep 30 done @@ -60,6 +65,10 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + with: + # The revision whose web deploy succeeded (this commit, or a later one + # that contains it), so the router never ships ahead of or behind it. + ref: ${{ steps.wait.outputs.sha || github.sha }} - name: Setup Node.js uses: actions/setup-node@v4 From b14f49b289d422756811c53d7d38ec54e94cdd80 Mon Sep 17 00:00:00 2001 From: Khaliq Date: Thu, 8 Oct 2026 01:05:12 -0700 Subject: [PATCH 3/3] ci(router): a failed gh api lookup replaces the value instead of appending Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/deploy-router.yml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy-router.yml b/.github/workflows/deploy-router.yml index 9e8b9386..0325c584 100644 --- a/.github/workflows/deploy-router.yml +++ b/.github/workflows/deploy-router.yml @@ -38,9 +38,12 @@ jobs: GH_TOKEN: ${{ github.token }} run: | for _ in $(seq 1 240); do - own=$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/deploy.yml/runs?head_sha=${GITHUB_SHA}&event=push&per_page=1" \ - --jq '.workflow_runs[0] | if . == null then "missing" else "\(.status) \(.conclusion // "")" end' \ - || echo "lookup-failed") + # On an HTTP error gh still prints the error body, so replace the whole + # value instead of appending to it. + if ! own=$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/deploy.yml/runs?head_sha=${GITHUB_SHA}&event=push&per_page=1" \ + --jq '.workflow_runs[0] | if . == null then "missing" else "\(.status) \(.conclusion // "")" end' 2>/dev/null); then + own="lookup-failed" + fi echo "web deploy for ${GITHUB_SHA::9}: ${own}" case "$own" in "completed success") echo "sha=${GITHUB_SHA}" >> "$GITHUB_OUTPUT"; exit 0 ;; @@ -51,10 +54,14 @@ jobs: # Own deploy is missing, queued, running or cancelled (superseded): # accept the newest successful web deploy if it contains this commit, # and deploy the router at THAT revision so router and pages match. - latest=$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/deploy.yml/runs?branch=main&event=push&status=success&per_page=1" \ - --jq '.workflow_runs[0].head_sha // ""' || echo "") + if ! latest=$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/deploy.yml/runs?branch=main&event=push&status=success&per_page=1" \ + --jq '.workflow_runs[0].head_sha // ""' 2>/dev/null); then + latest="" + fi if [ -n "$latest" ]; then - rel=$(gh api "repos/${GITHUB_REPOSITORY}/compare/${GITHUB_SHA}...${latest}" --jq '.status' || echo "") + if ! rel=$(gh api "repos/${GITHUB_REPOSITORY}/compare/${GITHUB_SHA}...${latest}" --jq '.status' 2>/dev/null); then + rel="" + fi echo "latest successful web deploy ${latest::9} is ${rel:-unknown} relative to this commit" case "$rel" in ahead|identical) echo "sha=${latest}" >> "$GITHUB_OUTPUT"; exit 0 ;; esac fi