Skip to content

auto-sync-upstream

auto-sync-upstream #63

Workflow file for this run

name: auto-sync-upstream
# Automatically syncs the fork with upstream google/adk-python.
#
# Flow (delegated to functions in scripts/lib/sync-core.sh):
# 1. Merge upstream into main. Content conflicts under .github/workflows/**
# auto-resolve via .gitattributes; modify/delete conflicts there
# auto-resolve in sync_merge_upstream. Real conflicts stop the merge.
# 2. Protect the two fork-owned workflow files (belt-and-braces, in case
# .gitattributes itself got rewritten by the same merge).
# 3. Install + run the model tests.
# 4. Tests pass -> publish (advance both main and stable).
# 5. Re-disable inherited upstream workflows that may have re-activated.
# 6. Any failure -> open (or comment on the existing) auto-sync issue.
#
# Consumers pin `@stable`, so they only ever receive green syncs and are
# automatically held at the last working version when a sync fails.
#
# WHY THE SHARED LIBRARY: this workflow AND scripts/update-fork.sh both
# source scripts/lib/sync-core.sh, so manual and automated recoveries
# execute mechanically-identical logic. Missing that parity caused an
# outage on 2026-07-02: the re-disable step lived only in this YAML, so
# a manual recovery via update-fork.sh left Continuous Integration
# active, which then failed on lint against our patch.
#
# Authentication model:
# Checkout + push use SYNC_TOKEN (a fine-grained PAT with `workflow: write`
# scope) instead of the built-in GITHUB_TOKEN. The built-in token cannot
# push changes under `.github/workflows/**` (GitHub gates that behind the
# `workflow` OAuth scope, which only PATs carry).
#
# Security: only static commands and trusted context values (github.run_id,
# github.repository, github.server_url) plus this workflow's own step outputs
# are used. No untrusted github.event.* input is interpolated into run steps.
on:
schedule:
- cron: '0 6 * * *' # daily 06:00 UTC
workflow_dispatch: {}
permissions:
contents: write # governs GITHUB_TOKEN (used by the failure-report step).
issues: write # The actual push uses SYNC_TOKEN, which is independent
actions: write # of these permissions.
concurrency:
group: auto-sync
cancel-in-progress: false
env:
INTEGRATION_BRANCH: main
STABLE_BRANCH: stable
UPSTREAM_REPO: https://github.com/google/adk-python.git
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Checkout integration branch (full history)
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
# PAT, not GITHUB_TOKEN: needed so the later push step can write
# changes under .github/workflows/**.
token: ${{ secrets.SYNC_TOKEN }}
- name: Configure git and merge drivers
run: |
. "$GITHUB_WORKSPACE/scripts/lib/sync-core.sh"
sync_configure_git
- name: Merge upstream
id: merge
run: |
. "$GITHUB_WORKSPACE/scripts/lib/sync-core.sh"
sync_ensure_upstream_remote
sync_merge_upstream
echo "before=$SYNC_BEFORE" >> "$GITHUB_OUTPUT"
echo "after=$SYNC_AFTER" >> "$GITHUB_OUTPUT"
echo "result=$SYNC_RESULT" >> "$GITHUB_OUTPUT"
# Belt-and-braces: if the same merge that changed our workflow files
# ALSO rewrote .gitattributes, merge=ours might not have been in
# effect. Re-restore our version of the two we own from pre-merge.
- name: Protect fork-owned workflows
if: steps.merge.outputs.result == 'merged'
env:
BEFORE: ${{ steps.merge.outputs.before }}
run: |
. "$GITHUB_WORKSPACE/scripts/lib/sync-core.sh"
sync_protect_fork_workflows "$BEFORE"
- name: Set up Python
if: steps.merge.outputs.result == 'merged'
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install
if: steps.merge.outputs.result == 'merged'
run: pip install -e ".[test]"
- name: Run model tests
id: tests
if: steps.merge.outputs.result == 'merged'
run: python -m pytest tests/unittests/models/ -q
# --- success path: merge clean + tests green -> advance both branches ---
- name: Publish new baseline
if: steps.merge.outputs.result == 'merged' && steps.tests.outcome == 'success'
run: |
. "$GITHUB_WORKSPACE/scripts/lib/sync-core.sh"
sync_publish_baseline
# Re-disable any inherited upstream workflows that may have re-activated.
# Runs on BOTH the merged-and-green path AND the up-to-date path so an
# inherited workflow that quietly re-activated between our syncs (e.g.
# because someone pushed to main directly, touching workflow files) still
# gets caught. See sync-core.sh's UNWANTED_INHERITED_WORKFLOWS list.
- name: Re-disable inherited workflows
if: >-
steps.merge.outputs.result == 'up-to-date' ||
(steps.merge.outputs.result == 'merged' && steps.tests.outcome == 'success')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
run: |
. "$GITHUB_WORKSPACE/scripts/lib/sync-core.sh"
sync_redisable_inherited_workflows
# --- nothing to do ---
- name: Up to date
if: steps.merge.outputs.result == 'up-to-date'
run: echo "Already in sync with upstream. Nothing to do."
# --- failure path: any failure (conflict, red tests, push rejection) -> hold + report ---
- name: Report sync failure
if: >-
failure() ||
steps.merge.outputs.result == 'conflict' ||
(steps.merge.outputs.result == 'merged' && steps.tests.outcome != 'success')
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Pin every gh call to THIS repo. We added `upstream` as a git remote
# earlier in the job; the gh CLI infers its default repo from git
# remotes and may pick the wrong one (a recent run targeted
# google/adk-python's labels API and got a 403).
REPO: ${{ github.repository }}
REASON: >-
${{ steps.merge.outputs.result == 'conflict' && 'merge conflict' ||
(steps.merge.outputs.result == 'merged' && steps.tests.outcome != 'success') && 'test failure' ||
'publish failed (see run log)' }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
STABLE_SHA=$(git rev-parse --short "origin/$STABLE_BRANCH")
TITLE="Upstream sync failed ($REASON) — holding stable at $STABLE_SHA"
BODY=$(cat <<EOF
Automated upstream sync could not complete.
- **Reason:** $REASON
- **\`$STABLE_BRANCH\` is held at:** \`$STABLE_SHA\` (last known-good — consumers are safe)
- **Run log:** $RUN_URL
### To recover
\`\`\`
./scripts/update-fork.sh
\`\`\`
Resolve the conflict or fix the failing test, push \`$INTEGRATION_BRANCH\`,
then fast-forward \`$STABLE_BRANCH\` to it.
EOF
)
# Ensure the label exists BEFORE any gh call that filters on it.
if ! gh label list --repo "$REPO" --search auto-sync --json name --jq '.[].name' | grep -qx auto-sync; then
gh label create auto-sync --repo "$REPO" --color B60205 --description "Automated upstream sync"
fi
EXISTING=$(gh issue list --repo "$REPO" --state open --label auto-sync --json number --jq '.[0].number')
if [ -n "$EXISTING" ]; then
gh issue comment "$EXISTING" --repo "$REPO" --body "$BODY"
else
gh issue create --repo "$REPO" --title "$TITLE" --body "$BODY" --label auto-sync
fi
exit 1