name: Sync Generated Provider Output on: push: branches: [main] paths: - ".claude-plugin/**" - "cli/engine/**" - "skill/**" - "scripts/**" - "package.json" - "bun.lock" workflow_dispatch: permissions: contents: write concurrency: group: sync-generated-output cancel-in-progress: false env: GENERATED_PATHS: >- .agents .claude .cursor .gemini .github/skills .grok .kiro .opencode .pi .qoder .rovodev .trae .trae-cn .vibe plugin jobs: sync: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v7 with: fetch-depth: 0 # Optional PAT or GitHub App token. With the default GITHUB_TOKEN, # GitHub suppresses follow-up workflow runs from the generated commit. token: ${{ secrets.SYNC_GENERATED_OUTPUT_TOKEN || github.token }} - name: Setup Node uses: actions/setup-node@v7 with: node-version: 24 - name: Setup Bun uses: oven-sh/setup-bun@v2 with: bun-version: latest - name: Install dependencies run: bun install --frozen-lockfile - name: Build release output run: bun run build:release - name: Check generated output drift id: drift run: | changes="$(git status --porcelain -- $GENERATED_PATHS)" if [ -z "$changes" ]; then echo "changed=false" >> "$GITHUB_OUTPUT" echo "No generated provider output drift." else echo "changed=true" >> "$GITHUB_OUTPUT" printf '%s\n' "$changes" fi - name: Commit generated output id: commit if: steps.drift.outputs.changed == 'true' run: | set -euo pipefail git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" # A push can lose the race against a human commit landing on main # during the ~30s build window (issue #388: ~10% of runs). Instead # of aborting and waiting for an unrelated push to re-trigger the # sync, re-sync to the latest main, rebuild against the current # source, and push again, with backoff. Every attempt builds from # a fresh origin/main, so pushed output always matches the source # state it lands on. for attempt in 1 2 3 4 5; do git add $GENERATED_PATHS if git diff --cached --quiet; then # The race that beat us was another sync (or the rebuilt output # matches the new main); nothing left to push. echo "No generated output drift after rebuild (attempt $attempt); nothing to push." echo "pushed=false" >> "$GITHUB_OUTPUT" exit 0 fi git commit -m "Sync generated provider output" git fetch origin main if git merge-base --is-ancestor origin/main HEAD && git push origin HEAD:main; then echo "Pushed generated output on attempt $attempt." echo "pushed=true" >> "$GITHUB_OUTPUT" exit 0 fi # The recovery work is pointless on the final attempt: nothing # would consume the rebuild, and the backoff would only delay # the failure. if [ "$attempt" = 5 ]; then break; fi echo "Push did not land on attempt $attempt (main advanced, or the push itself failed); re-syncing and rebuilding." git reset --hard origin/main bun install --frozen-lockfile bun run build:release sleep $((attempt * 5)) done echo "::error::Could not push generated output after 5 attempts (main kept advancing, or pushes kept failing); rerun this workflow on the latest main." exit 1 - name: Summarize generated output commit if: steps.drift.outputs.changed == 'true' run: | if [ "${{ steps.commit.outputs.pushed }}" = "true" ]; then echo "Committed generated provider output directly to main." >> "$GITHUB_STEP_SUMMARY" else echo "Generated output drift resolved itself after a mid-run rebuild; nothing was pushed." >> "$GITHUB_STEP_SUMMARY" fi