Skip to content

Sync SDK repos

Sync SDK repos #1743

Workflow file for this run

name: Sync SDK repos
# Keeps the staging and production trunks in sync and the config repo's
# tracking files fresh. Each job self-routes by repo + event, so this one file
# can live in both repos and only the right job runs. The dispatch jobs are
# eager-only — the polls cover them if a dispatch fails.
on:
schedule:
# back-sync poll: a cheap pure-git check, twice hourly so an unsynced production
# change (e.g. a community PR between releases) can't hold codegen for long.
- cron: '7,37 * * * *'
workflow_dispatch: {}
# repository_dispatch:
# types: [prod-released]
# release:
# types: [published]
push:
# main only. stlc preview/integrated/codegen branches never push to main.
branches: [main]
jobs:
back-sync:
# Fast-forward production main back onto staging so the trunks stay identical.
runs-on: ubuntu-latest
if: >-
github.repository == 'togethercomputer/together-typescript-staging' &&
(github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || github.event_name == 'repository_dispatch')
permissions:
contents: write
concurrency:
group: stlc-back-sync
cancel-in-progress: true
env:
PRODUCTION_REPO: togethercomputer/together-typescript
steps:
- name: Check out staging
uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
- name: Mint STLC app token (scoped to SDK staging repos)
id: app-token
uses: actions/create-github-app-token@v3
with:
client-id: ${{ secrets.STLC_WORKFLOW_APP_CLIENT_ID }}
private-key: ${{ secrets.STLC_WORKFLOW_APP_PRIVATE_KEY }}
owner: togethercomputer
repositories: |
together-typescript-staging
together-typescript
- name: Configure auth for github
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
git config --global user.name "stlc-workflow-app[bot]"
git config --global user.email "287504455+stlc-workflow-app[bot]@users.noreply.github.com"
gh auth setup-git
- name: Fetch production main
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
git remote add production "https://x-access-token:${GH_TOKEN}@github.com/${PRODUCTION_REPO}.git"
git fetch production main
- name: Check whether production is ahead of staging
id: diff
run: |
# Content compare: would merging production into staging change its tree?
# If not, staging already has production's content (release-please commits).
MERGED=$(git merge-tree --write-tree origin/main production/main) || MERGED=conflict
STAGING_TREE=$(git rev-parse 'origin/main^{tree}')
if [ "$MERGED" = "$STAGING_TREE" ]; then
echo "Staging already has production's content. Nothing to pull back."
echo "behind=false" >> "$GITHUB_OUTPUT"
else
echo "behind=true" >> "$GITHUB_OUTPUT"
fi
- name: Sync production to staging (fast-forward)
if: steps.diff.outputs.behind == 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
set -euo pipefail
# Prefer a SHA-preserving fast-forward. If the trunks have forked
# (staging is not an ancestor of production), open a heal PR instead.
if git -c credential.helper= merge-base --is-ancestor origin/main production/main; then
git -c credential.helper= push "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" production/main:refs/heads/main
echo "Fast-forwarded staging/main to production/main."
exit 0
fi
echo "::warning title=Back-sync blocked::staging main is not an ancestor of production/main — opening a heal PR."
branch="stlc/heal-promote-ancestry"
staging_url="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git"
git checkout -B "$branch" origin/main
if ! git -c credential.helper= merge production/main -m "Merge production into staging (heal promote ancestry)"; then
echo "::error title=Heal merge conflicted::Could not auto-merge production into staging. Resolve ancestry manually."
git merge --abort || true
exit 1
fi
git -c credential.helper= push --force "$staging_url" "HEAD:refs/heads/$branch"
EXISTING_PR=$(gh pr list \
--repo "${GITHUB_REPOSITORY}" \
--head "$branch" \
--state open \
--json number \
--jq '.[0].number // empty')
if [ -z "$EXISTING_PR" ]; then
gh pr create \
--repo "${GITHUB_REPOSITORY}" \
--base main \
--head "$branch" \
--title "Merge production into staging (heal promote ancestry)" \
--reviewer blainekasten \
--body "$(cat <<'EOF'
Staging and production have forked, so a fast-forward back-sync is unsafe.
This PR merges `production/main` into staging to restore ancestry so promote/back-sync can fast-forward again. Review carefully — both trunks had unique commits.
EOF
)"
echo "Opened heal PR and requested review from blainekasten."
else
echo "Heal PR #${EXISTING_PR} already open; branch force-pushed."
gh pr edit "$EXISTING_PR" --repo "${GITHUB_REPOSITORY}" --add-reviewer blainekasten || true
fi
# Optional alerting workflow.
# If we want to add this back in, we need an incoming webhook (Slack/Teams/etc) that the workflow POSTs to when a run fails, so a stalled build or tracking sync pages a human instead of sitting red in the Actions tab.
#
# - 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 back-sync (sync from production) failed in ${{ github.repository }}. A stalled 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
# notify-back-sync:
# # On a published release, tell staging to back-sync now instead of waiting for
# # the poll. Dispatch-only: it cannot write production or staging contents.
# runs-on: ubuntu-latest
# if: >-
# github.repository == 'togethercomputer/together-typescript' &&
# (github.event_name == 'release' || github.event_name == 'workflow_dispatch')
# permissions:
# contents: read
# env:
# STAGING_REPO: togethercomputer/together-typescript-staging
# steps:
# - name: Mint STLC app token (scoped to SDK staging repos)
# id: app-token
# uses: actions/create-github-app-token@v3
# with:
# client-id: ${{ secrets.STLC_WORKFLOW_APP_CLIENT_ID }}
# private-key: ${{ secrets.STLC_WORKFLOW_APP_PRIVATE_KEY }}
# owner: togethercomputer
# repositories: together-typescript-staging
# - name: Dispatch back-sync to staging
# env:
# DISPATCH_TOKEN: ${{ steps.app-token.outputs.token }}
# REF_NAME: ${{ github.ref_name }}
# run: |
# set -euo pipefail
# payload=$(jq -n --arg ref "$REF_NAME" '{event_type:"prod-released",client_payload:{ref:$ref}}')
# code=$(curl -sS -o /tmp/dispatch.txt -w '%{http_code}' -X POST \
# -H "Authorization: Bearer ${DISPATCH_TOKEN}" \
# -H "Accept: application/vnd.github+json" \
# -H "X-GitHub-Api-Version: 2022-11-28" \
# "https://api.github.com/repos/${STAGING_REPO}/dispatches" \
# -d "$payload")
# if [ "$code" = "204" ]; then
# echo "Back-sync dispatched to ${STAGING_REPO}."
# else
# echo "Dispatch failed (HTTP $code)" >&2; cat /tmp/dispatch.txt >&2; exit 1
# fi
# Optional alerting workflow.
# If we want to add this back in, we need an incoming webhook (Slack/Teams/etc) that the workflow POSTs to when a run fails, so a stalled build or tracking sync pages a human instead of sitting red in the Actions tab.
#
# - 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 release back-sync trigger failed in ${{ github.repository }} — the staging repo was NOT notified to back-sync this release. Staging catches up on its next poll, but verify the app token. 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
seal-dispatch:
# When out-of-band custom code lands on staging main, tell the config repo to
# re-seal now instead of waiting for its scheduled sync. The loop guards skip
# stlc's own pushes, so the bot's commits can't trigger a re-seal loop.
runs-on: ubuntu-latest
if: >-
github.repository == 'togethercomputer/together-typescript-staging' &&
github.event_name == 'push'
permissions:
contents: read
concurrency:
group: seal-dispatch-${{ github.ref }}
cancel-in-progress: false
env:
CONFIG_REPO: togethercomputer/openapi
steps:
- name: Mint STLC app token (scoped to SDK staging repos)
id: app-token
uses: actions/create-github-app-token@v3
with:
client-id: ${{ secrets.STLC_WORKFLOW_APP_CLIENT_ID }}
private-key: ${{ secrets.STLC_WORKFLOW_APP_PRIVATE_KEY }}
owner: togethercomputer
repositories: openapi
- name: Loop-guard and send re-seal dispatch
env:
DISPATCH_TOKEN: ${{ steps.app-token.outputs.token }}
HEAD_MSG: ${{ github.event.head_commit.message }}
HEAD_AUTHOR_NAME: ${{ github.event.head_commit.author.name }}
ACTOR: ${{ github.actor }}
SHA: ${{ github.sha }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
# Loop guard 1: skip pushes by the workflow app (back-sync FF, stlc regenerate).
# App-token pushes re-trigger workflows; commit author/message alone miss back-sync
# tips authored by release-please/humans.
if [ "$ACTOR" = "stlc-workflow-app[bot]" ]; then
echo "Push actor is stlc-workflow-app[bot] — skipping re-seal dispatch."
exit 0
fi
# Loop guard 2: skip the stlc "Build SDK" squash commit (Stainless-Generated-From trailer).
if printf '%s' "$HEAD_MSG" | grep -q 'Stainless-Generated-From'; then
echo "Head commit is an stlc build — skipping re-seal dispatch."
exit 0
fi
# Loop guard 3: skip stlc-workflow-app[bot] commits (e.g. a regeneration commit).
if [ "$HEAD_AUTHOR_NAME" = "stlc-workflow-app[bot]" ]; then
echo "Head commit authored by stlc-workflow-app[bot] — skipping re-seal dispatch."
exit 0
fi
payload=$(jq -n --arg sha "$SHA" --arg repo "$REPO" \
'{event_type:"seal-custom-code",client_payload:{target:"all",sha:$sha,repo:$repo}}')
code=$(curl -sS -o /tmp/dispatch.txt -w '%{http_code}' -X POST \
-H "Authorization: Bearer ${DISPATCH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/${CONFIG_REPO}/dispatches" \
-d "$payload")
if [ "$code" = "204" ]; then
echo "Re-seal dispatched to ${CONFIG_REPO}."
else
echo "Dispatch failed (HTTP $code)" >&2; cat /tmp/dispatch.txt >&2; exit 1
fi
# Optional alerting workflow.
# If we want to add this back in, we need an incoming webhook (Slack/Teams/etc) that the workflow POSTs to when a run fails, so a stalled build or tracking sync pages a human instead of sitting red in the Actions tab.
#
# - 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 seal-dispatch failed in ${{ github.repository }} — the config repo was NOT notified to re-seal. The config repo's scheduled sync is the backstop, but verify the app token. 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