Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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: |
Expand Down Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions docs/deployment/GITHUB_DEPLOY_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <that-branch>`.** 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 <that-branch>`.** To deploy a specific commit, dispatch on main and pass the
> commit as an input: `deploy-staging.yml -f ref=<sha>` first (it posts `staging-smoke` on that SHA),
> then `deploy-dev.yml -f ref=<full 40-char sha>`. The commit must be pushed to the repo (a
> `deploy/<name>` 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.
Expand Down
Loading