Dispatch, watch, and verify a UAT deployment of hushh-research through the governed GitHub Actions lane. Use when asked to deploy to UAT, ship a merged SHA to UAT, check why a UAT dispatch was refused, or when a dispatch attempt produces no workflow run at all. Works for any teammate whose GitHub login is in the UAT allowlist; encodes the token-scope trap that makes dispatches silently never reach GitHub.
Install
npx skillscat add hushh-labs/hushh-research/deploy-uat Install via the SkillsCat registry.
Deploy to UAT (hushh-research)
A dispatch is not a deploy. A green workflow is not a running system. Done means the new
revision is serving and the live surface answers.
Nothing auto-deploys on merge in this repo. All deploys are manual workflow_dispatch ofdeploy-uat.yml, from main only.
0. Preconditions — check these before dispatching
Who am I?
gh auth statusNote the login. Everything below is gated on that exact GitHub username.
Am I on the UAT allowlist? The workflow's first job runs
scripts/ci/assert-governed-actor.py, which checks the dispatching actor againstconfig/ci-governance.json → uat.manual_dispatch_users:python3 -c "import json; print(json.load(open('config/ci-governance.json'))['uat']['manual_dispatch_users'])"Not listed → do not dispatch. Ask a governance owner (
kushaltrivedi5orankitkumarsingh1702) for achore(governance)PR adding you. That is the whole
grant process; there is no GitHub-settings step.Does my token have the
workflowscope? This is the trap that looks like nothing:
the dispatch fails client-side and no run is ever created — the Actions page stays
empty, no error appears in the repo. If your dispatches never show up in the run list:gh auth refresh -h github.com -s repo,workflow
1. Pick the SHA
Deploy an exact, immutable commit that is on main — never a branch name.
git fetch origin --prune
SHA=$(git rev-parse origin/main) # or the mergeCommit of the PR you shipped
git merge-base --is-ancestor "$SHA" origin/main && echo "on main: ok"Check the post-merge smoke for that SHA is green, not queued — the deploy gate treats a
queued smoke run exactly like a red one:
gh run list --repo hushh-labs/hushh-research --workflow=main-post-merge-smoke.yml --limit 32. Dispatch
gh workflow run deploy-uat.yml --repo hushh-labs/hushh-research \
--ref main -f scope=auto -f sha="$SHA"--ref mainis mandatory. Theuatenvironment's branch policy and the workflow itself
both refuse any other ref.scope=autodetects backend/frontend from the diff. Deploying one lane by hand is half a
release — letautodecide unless you have a specific reason.
3. Watch it to completion
sleep 10
RUN_ID=$(gh run list --repo hushh-labs/hushh-research --workflow=deploy-uat.yml \
--limit 1 --json databaseId --jq '.[0].databaseId')
gh run watch "$RUN_ID" --repo hushh-labs/hushh-research --exit-statusIf the run fails at "Assert manual UAT dispatch actor policy", you are not in the
allowlist (step 0.2). If no run appears at all, it is the token scope (step 0.3).
4. Verify on the real thing
# Live journey checks (no GCP access needed)
python3 scripts/ops/verify_live_environment.py --env uatThen open https://uat.one.hushh.ai and walk the flow you shipped — loading, empty, error
states, not just the happy path. Testing from India? US-only directories legitimately return
zero rows on HTTP 200 — follow with a US ZIP (98033) before calling a surface dead.
If you have GCP viewer access, also confirm the serving revision (reads on hushh-pda-uat
work; writes are IAM-denied for everyone — that is deliberate):
gcloud run services describe consent-protocol --region us-central1 --project hushh-pda-uat \
--format=json | python3 -c "
import json,sys; st=json.load(sys.stdin)['status']
live=next((t for t in st.get('traffic',[]) if t.get('percent')==100), None)
print('serving:', live and live['revisionName'], '| latest:', st.get('latestReadyRevisionName'))
print('IN SYNC' if live and live['revisionName']==st.get('latestReadyRevisionName') else '*** NOT SERVING LATEST ***')"Hard rules
- Never deploy UAT with
gcloud run deployby hand. A deliberate IAM deny policy onhushh-pda-uatblocks manual Cloud Run writes for every human, including project Owners.
The workflow is the only lane. - Production is a different workflow (
deploy-production.yml), a different allowlist
(2 people), and requires being asked for in those words. UAT access is not production
authorization. - Report with evidence: run URL + conclusion, the SHA deployed, and what you saw on the
live surface — not "should be live".