Skip to content

Promote SDKs

Promote SDKs #28

Workflow file for this run

name: Promote SDKs
# Promote staging to production by fast-forwarding production main up to staging,
# preserving SHAs so the trunks stay identical and linear (nothing to heal on the
# next stlc build). This is a git-only reconciliation, not a release: release-please
# owns the actual version bump/publish gate on production. Runs on every push to
# staging main, because any staging-only commit (stlc build or not) forks the trunks
# against the next production merge; the daily schedule is a backstop and
# workflow_dispatch an eager promote (back-sync dispatches it on a fork). A fork
# that merges cleanly and passes staging CI is healed with a merge commit on both
# trunks (the only non-linear history this writes); anything else fails for a person.
# Every run is a safe no-op when there's nothing to promote.
on:
push:
branches: [main]
schedule:
# 8am PT (15:00 UTC); shifts to 7am during PST since GitHub cron doesn't track DST.
- cron: '0 15 * * *'
workflow_dispatch: {}
permissions:
contents: read
actions: read
jobs:
promote:
# Runner comes from the STLC_RUNNER repo/org variable when set; defaults to GitHub-hosted.
runs-on: ${{ vars.STLC_RUNNER || 'ubuntu-latest' }}
if: github.repository == 'runloopai/api-client-python-staging'
# Optional gate: add required reviewers to this environment to approve each
# promote. With none it only scopes secrets and adds no gate. Remove if unused.
environment: production
concurrency:
group: stlc-promote
cancel-in-progress: true
env:
PRODUCTION_REPO: runloopai/api-client-python
GH_TOKEN: ${{ secrets.PRODUCTION_REPO_TOKEN }}
steps:
- name: Check out staging
uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
- name: Fetch production main
run: |
git remote add production \
"https://x-access-token:${GH_TOKEN}@github.com/${PRODUCTION_REPO}.git"
git fetch production main
- name: Check whether production already has staging's content
id: diff
run: |
# After a release, production has release-please commits staging lacks, so compare trees.
MERGED=$(git merge-tree --write-tree production/main origin/main) || MERGED=conflict
PRODUCTION_TREE=$(git rev-parse 'production/main^{tree}')
# A fork needs healing even when production already has staging's content.
if ! git merge-base --is-ancestor origin/main production/main &&
! git merge-base --is-ancestor production/main origin/main; then
echo "Staging and production main have forked."
echo "synced=false" >> "$GITHUB_OUTPUT"
elif [ "$MERGED" = "$PRODUCTION_TREE" ]; then
echo "Production already contains staging's content. Nothing to promote."
echo "synced=true" >> "$GITHUB_OUTPUT"
else
echo "synced=false" >> "$GITHUB_OUTPUT"
fi
- name: Promote staging to production (fast-forward)
if: steps.diff.outputs.synced == 'false'
env:
PRODUCTION_REPO_TOKEN: ${{ secrets.PRODUCTION_REPO_TOKEN }}
GH_TOKEN: ${{ github.token }}
run: |
# Production not an ancestor of staging means the trunks have forked
# (production advanced without a back-sync) and FF is unsafe: heal with a
# clean merge commit, else fail.
if ! git merge-base --is-ancestor production/main origin/main; then
bash .github/scripts/stlc-heal-fork.sh
exit 0
fi
git push production origin/main:refs/heads/main
echo "Fast-forwarded production/main to staging/main."
- name: Alert on failure
if: failure()
env:
ALERT_WEBHOOK_URL: ${{ secrets.STLC_ALERT_WEBHOOK_URL }}
run: |
run_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
msg="stlc promote failed in ${{ github.repository }}. A stalled promote or back-sync lets custom-code tracking drift, which later builds refuse on — investigate before the next build. Run: $run_url"
echo "::error title=stlc workflow failed::$msg"
{ echo "### ⚠️ stlc workflow failed"; echo ""; echo "$msg"; } >> "$GITHUB_STEP_SUMMARY"
if [ -n "${ALERT_WEBHOOK_URL:-}" ]; then
curl -sS -X POST -H 'Content-Type: application/json' \
-d "$(jq -n --arg text "$msg" '{text:$text}')" "$ALERT_WEBHOOK_URL" \
|| echo "::warning::Alert webhook POST failed"
fi