From ce20f86a4251e1dfbcdd0012949c95baf9dd1d7b Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 25 Oct 2026 17:10:53 -0700 Subject: [PATCH] ci(deploy-dev): accept a commit SHA to deploy, dispatched on main Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VD3azQJZrdMmw8g51iscWD --- .github/workflows/deploy-dev.yml | 42 ++++++++++++++++++++++++-- docs/deployment/GITHUB_DEPLOY_SETUP.md | 8 +++-- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index c4730e8c3..4a1b5795d 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -12,7 +12,11 @@ on: workflow_dispatch: inputs: image_tag: - description: 'Image tag override (defaults to short SHA of HEAD)' + description: 'Image tag override (defaults to the short SHA of the deployed commit)' + required: false + default: '' + ref: + description: 'Full 40-character commit SHA to deploy (defaults to main HEAD). It needs a green staging-smoke.' required: false default: '' allow_without_staging_smoke: @@ -43,14 +47,47 @@ jobs: # commit status on this exact SHA. Deploy Staging runs the Tier-1 UI smoke and posts that # status; this job reads it and blocks the deploy if it is not success. The # allow_without_staging_smoke input is the explicit emergency override. + # + # The run is always dispatched on main, which the deploy identity trusts. `inputs.ref` picks + # which commit it builds: a commit that is not main HEAD, e.g. a release commit holding main's + # reviewed changes up to a point plus a cherry-picked fix. Only a full SHA is accepted, and the + # deploy job checks out the SHA this job resolved, never a branch name. require_staging_smoke: name: Require green staging-smoke on this SHA runs-on: ubuntu-latest + outputs: + sha: ${{ steps.resolve.outputs.sha }} steps: + - name: Resolve the commit to deploy + id: resolve + env: + GH_TOKEN: ${{ github.token }} + REF: ${{ inputs.ref }} + HEAD_SHA: ${{ github.sha }} + REPO: ${{ github.repository }} + run: | + set -euo pipefail + if [ -z "$REF" ]; then + echo "sha=$HEAD_SHA" >> "$GITHUB_OUTPUT" + echo "Deploying main HEAD $HEAD_SHA" + exit 0 + fi + if ! printf '%s' "$REF" | grep -Eq '^[0-9a-f]{40}$'; then + echo "::error title=ref::ref must be a full 40-character lowercase commit SHA (got: $REF)" + exit 1 + fi + FOUND=$(gh api "repos/$REPO/commits/$REF" --jq .sha 2>/dev/null || echo "") + if [ "$FOUND" != "$REF" ]; then + echo "::error title=ref::commit $REF is not in $REPO" + exit 1 + fi + echo "sha=$REF" >> "$GITHUB_OUTPUT" + echo "::notice title=ref::Deploying $REF instead of main HEAD $HEAD_SHA" + - name: Gate on the staging-smoke commit status env: GH_TOKEN: ${{ github.token }} - SHA: ${{ github.sha }} + SHA: ${{ steps.resolve.outputs.sha }} REPO: ${{ github.repository }} ALLOW: ${{ inputs.allow_without_staging_smoke }} run: | @@ -80,6 +117,7 @@ jobs: - name: Checkout (with submodules for _external/clawdbot) uses: actions/checkout@v4 with: + ref: ${{ needs.require_staging_smoke.outputs.sha }} submodules: recursive - name: Compute image tag diff --git a/docs/deployment/GITHUB_DEPLOY_SETUP.md b/docs/deployment/GITHUB_DEPLOY_SETUP.md index 6c6b6b30e..85647d25a 100644 --- a/docs/deployment/GITHUB_DEPLOY_SETUP.md +++ b/docs/deployment/GITHUB_DEPLOY_SETUP.md @@ -214,8 +214,12 @@ jobs: > token's `ref` does not match the binding's attribute condition. (OIDC *federation* succeeds; the > denial is specifically at service-account **impersonation** — `getAccessToken` — which the binding > gates.) **So you cannot deploy an arbitrary non-`main` SHA by pushing it to a side branch and -> dispatching `--ref `.** To deploy a specific commit, get it onto `main` and dispatch -> `--ref main` (it builds `HEAD`). Earned 2026-10-23: a pinned **staging** run (`deploy-staging` on a +> dispatching `--ref `.** To deploy a specific commit, dispatch on main and pass the +> commit as an input: `deploy-staging.yml -f ref=` first (it posts `staging-smoke` on that SHA), +> then `deploy-dev.yml -f ref=`. The commit must be pushed to the repo (a +> `deploy/` branch holding main's commits up to a point plus a cherry-picked fix, for +> example); Deploy Dev accepts only a full SHA and refuses it without a green staging-smoke on it. +> With no `ref`, both build main `HEAD`. Earned 2026-10-23: a pinned **staging** run (`deploy-staging` on a > side branch, run 39955031436) failed auth this way; `deploy-dev` (prod) is bound to `main` + the > `prod` environment the same way (step 4), so a pinned prod deploy would fail identically — not > separately attempted.