Promote to Prod
The user has a change they want in production, proven. Your job is the whole ladder: staging PR → green → merge → verify on staging → main PR → green → merge → verify production is healthy and the fix is actually live → keep evidence → update the Jira ticket.
This is the step after [[shipit]] leaves a review-ready PR and beside
[[cut-release]] (which turns merged code into a versioned build/store submission).
If the repo's production path is a store submit or an OTA push rather than a merge
into main, this skill stops at that boundary — see "Where this skill stops".
Conductor, not the orchestra. Lean on what exists:
| Step | Existing skill / tool |
|---|---|
| Ticket, ACs, links; the post-prod comment + move to Done / Ready for QA | jira-cli |
| PR body for either PR | pr-description |
| Review the change before promoting it | pr-review / review-cycle |
| Prove the change against its ACs | qa |
| Verify on staging / prod in a browser | Chrome MCP, playwright-cli |
| Dashboards, deploys, runtime errors | repo CLAUDE.md "Dashboards & Data Sources", Vercel MCP |
| Prior promotion gotchas | vault-keeper |
| The ship update | write-slack-message |
Read the repo's CLAUDE.md first — the branch names live there, not here
Never assume staging and main. Read the repo's CLAUDE.md and confirm:
- The actual branch ladder —
staging/develop/preprod→main/master/production. Some repos promote by tag or by a Vercel promotion, not a merge. - How prod deploys — auto-deploy on merge, a manual promotion, a release workflow. This decides what "wait for prod" even means.
- The staging and prod hostnames — you need the real URLs to verify anything.
- Merge method — squash/merge/rebase, and whether the repo requires a linear history.
- Quality gates — the lint/typecheck/test commands, in case CI doesn't run them all.
The four lines that matter most
1. Green means green — never merge on red, pending, or "probably fine".
gh pr checks <n> --watch until it settles, then read the result. A PR with no
required checks configured is not the same as a PR whose checks passed — say which
one you're looking at. If a check is flaky, re-run it; don't wave it through.
2. The staging gate is the whole point — do not open the main PR until staging says the change is safe. Merging to staging is not verifying staging. After the staging merge, confirm the staging deploy actually finished and carries your commit, then exercise the change there and smoke-check that nothing adjacent broke. If the change can't be exercised on staging (no data path, no staging deploy for this surface), say so explicitly and get the user's go-ahead — never let "couldn't test it" quietly become "tested it".
3. Check the deploy, not the merge. A merged PR is not a deployed change. Before you verify behavior on either environment, confirm the running deployment's commit SHA matches what you merged. Verifying against the previous build is how a broken promotion gets signed off.
4. If prod comes back broken, revert first and tell the user immediately. You have standing authority to open a revert PR and merge it the moment production verification fails — that is the fastest safe state, and you don't wait for a reply to take it. Then report: what broke, what you reverted, and how you confirmed prod recovered. Say it FIRST, before anything else in the message.
Merge authority (what the user granted this skill)
You merge both PRs yourself, without asking, only when all of these hold:
- Every check on the PR is green (not pending, not skipped-because-red).
- For the main PR specifically: staging was verified — the change works there and nothing adjacent broke — or the user explicitly waived it for a change staging can't exercise.
- The diff is the change being promoted and nothing else (no stray commits swept in).
If any of those fails, stop and say which one. That's the gate, not a formality.
Where this skill stops
Merging a PR is in scope. These are never yours to run, regardless of the ladder:
eas submit, App Store / Play Store submission or release- OTA pushes (
eas update,pnpm ota) - Database schema changes / migrations pushed to prod
If the repo's production path runs through one of those, take it to the edge, print the exact command, and hand it back (that's [[cut-release]]'s territory).
The pipeline
1. Scope what's being promoted
Pin down the change: the branch, the ticket (jira-cli), the PR if one exists, and
what "fixed" means concretely — the specific observable behavior you'll confirm on
prod later. If the user gave you a ticket key, pull its ACs; they become the prod
verification list. Check vault-keeper for prior gotchas on this promotion path.
Confirm the working tree is clean and the branch holds only this work. If the change
hasn't been reviewed, offer pr-review/review-cycle before promoting rather than
after.
2. PR into staging
Create the PR against the staging branch (branch creation rules still apply:
--no-track, never set a base branch as upstream). Body via pr-description; the
ticket key in the title and body, the PR URL back on the ticket — both directions, at
creation time. No AI attribution anywhere.
gh pr create --base <staging-branch> --head <branch> --title "<KEY>: <what>" --body-file <file>
3. Wait for checks, then merge
gh pr checks <n> --watch
Read the settled result. All green → merge with the repo's merge method. Red → the failure is a real finding: surface the actual error, fix it on the branch, push, and re-watch. Don't merge around a failing check.
4. Verify on staging — the gate
Confirm the staging deploy finished and its commit matches your merge. Then:
- Exercise the change on the real staging host — Chrome MCP or
playwright-clifor UI, a keyed API call / query for anything else. Useqaif the ticket has a real AC list worth walking. - Smoke-check what's adjacent — the pages/endpoints the change touches, plus the app's main flow. You're looking for collateral damage, not just your own fix.
- Check the logs — staging runtime errors after the deploy, not before it.
Broken, or the change doesn't work there → stop. No main PR. Report what you saw with evidence and hand it back (or fix it and re-run the ladder from step 2). Not exercisable on staging → say that plainly and ask before continuing.
5. PR into main
Staging → main, checks, merge — same discipline as steps 2 and 3, including
gh pr checks <n> --watch until every check settles and merging only on all green. The
main PR body
says what's in it (the tickets/PRs since the last promotion) rather than restating the
one ticket, when the promotion sweeps in other merged work. Watch for exactly that:
a staging → main PR carries everything else sitting on staging. Read the diff and
tell the user what else is riding along before you merge it.
A red check on the main PR is not fixed on that PR. Its head is the staging branch, so
"fix it on the branch and push" would mean pushing straight to staging. Stop instead. Read
the failing run's logs (gh run view <run-id> --log-failed) and work out whether the failure
comes from your change or from other work riding along from staging. If it's yours, fix it on
a new --no-track branch, open a new PR into staging, and restart the ladder from step 2. If
it's someone else's, report it with the evidence and ask the user how to proceed. Either way,
the main PR stays unmerged.
6. Verify production
Same shape as step 4, higher stakes, in this order:
- The deploy landed — production is serving your merge commit. Check the deploy job / Vercel deployment, not the merge.
- Nothing broke — the main flow still works, error rates and runtime logs are flat against the pre-deploy baseline (look at the dashboard before the merge so you have something to compare to), key endpoints return what they should.
- The fix is actually live — exercise the specific behavior from step 1 on the
real production host, via Chrome MCP /
playwright-clifor UI orcurlfor an endpoint. This is the claim the whole skill exists to support; "it merged" is not it.
Failure at any of the three → revert immediately per hard rule 4, then confirm prod recovered before you write anything else.
7. Capture evidence
Save it locally to ~/Desktop/<TICKET-OR-SLUG>-prod-evidence/ with names that say what
they prove (1-prod-checkout-loads-fixed.png, 2-prod-api-returns-200.txt), not
screenshot1.png. Take whatever the interface produces:
- UI — screenshot of the fixed behavior on the prod host, URL visible in frame. Label BEFORE/AFTER explicitly in your message text, not just the filename.
- API — the actual
curlinvocation and its response body, saved to a.txt. - Data/logs — the query and its result, or the log line, copied verbatim.
- The deployed commit — the SHA prod is running, so the evidence is pinned to a build.
Evidence for what production did before the fix is worth far more than evidence after it alone. Grab it in step 6's baseline if you can.
8. Ask where the evidence should be posted
Local folder is the default and the delivery. Once prod is verified, ask whether to publish it — offer: the GitHub PR, the Jira ticket, both, or nowhere. Don't pick silently.
If they say post it, embed the evidence inline (a reader shouldn't open attachments one
by one) and never cite a local path in outgoing content — upload the image into the
artifact. Jira inline images need the media-services UUID, not the numeric attachment
id; GitHub takes gh image <files> --repo owner/name. qa step 6 has the full upload
dance if you need it.
9. Update the Jira ticket
Only when the promotion has a ticket: a key the user gave, or one in the PR title, branch name, or body. No ticket → skip this step entirely, and don't ask. Never run it when prod verification failed or you reverted — a ticket moved forward on a broken deploy is worse than one left behind.
For each ticket in the promotion (a staging → main PR that swept in other merged work
carries their tickets too — handle every one the same way), with jira-cli:
- Comment what shipped. Short: the feature PR and the staging → main PR as links,
the commit production is running, and how you verified it on prod. ADF, tickets as
inlineCardlinks, evidence uploaded into the comment if the user chose Jira in step 8. No local paths. - Move it to Done or Ready for QA — whichever the ticket calls for.
- Ready for QA: the ticket has QA Instructions, a QA owner, or acceptance criteria that someone else still needs to test.
- Done: you proved every AC on production during this promotion and nothing is left for QA.
- Not obvious → ask the user, offering both, before transitioning. Don't guess.
- GET the transitions first — ids differ per issue type, so never reuse one from another ticket. POST the transition, then re-read the status to confirm it landed: a wrong id returns the same empty 204 as a correct one.
jira-curl <instance> GET /rest/api/3/issue/<KEY>/transitions | jq '.transitions[] | {id, name}'
jira-curl <instance> POST /rest/api/3/issue/<KEY>/transitions -d '{"transition":{"id":"<id>"}}'
jira-curl <instance> GET "/rest/api/3/issue/<KEY>?fields=status" | jq -r '.fields.status.name'
10. Report the real state
Plainly: what merged where, what you verified on staging, what you verified on prod and
how, what rode along in the promotion, and anything you could not verify. Log it
with save-session-to-worklog; file any new promotion gotcha with vault-keeper. If a
Slack update is wanted, draft it with write-slack-message — verdict first.
Traps that have burned this before
- Pending is not green.
gh pr mergecan succeed while checks are still running. Watch them to settled, then read the verdict. - The staging → main PR is bigger than your change. Everything else on staging goes with it. Read that diff and name what's riding along before merging.
- Cached / stale deploy. A green deploy job doesn't guarantee the CDN is serving the new bundle. Hard-reload, or check the asset hash / commit SHA the page reports.
- Verifying the wrong host. A staging URL that looks like prod, or a preview URL from the PR. Read the hostname in the screenshot before you call it prod evidence.
- Auth-walled prod. The fix may only be visible behind a login. Plan the account you will use before you get there, and never put credentials in evidence files.
- Browser tool lies. Never trust a browser tool's success response — assert the
post-condition (read back
window.innerWidthafter a resize, read the DOM after a click). If screenshots time out twice, fall back to a DOM/accessibility read and say so. - Access blocks stop the run. A 401/403 on a dashboard, staging host, or prod login is not something to route around — name exactly what access is needed and ask for it.
What "done" looks like
Both PRs merged on green checks, the change verified working on staging before the main
PR ever opened, production confirmed to be running the merged commit, healthy, and
carrying the fix — with evidence in ~/Desktop/<TICKET-OR-SLUG>-prod-evidence/ and the
user asked where (if anywhere) it should be posted. Every Jira ticket in the promotion
carries a what-shipped comment and sits in Done or Ready for QA, confirmed by re-reading
its status (skipped when there is no ticket). Nothing submitted to a store, no OTA
pushed, no migration run. If prod failed verification: reverted immediately, the revert
confirmed live, the user told first, and no ticket moved.