From 4afb366844ea5e951d6c7bfe1e561ead12f885a7 Mon Sep 17 00:00:00 2001 From: evanorti <87997759+evanorti@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:38:38 -0400 Subject: [PATCH] fix(ci): check out the sync branch before transforming, and serialize runs Mirrors cosmos/example#16 and #17 in the reverse direction. The workflow transformed the tutorials into cosmos-example/ while still on main, then stashed, switched to the open sync branch, and popped. Popping a stash taken against main onto a branch that already carries overlapping changes conflicts and exits 1, which fails the step under bash -e. That is the likely cause of the unexplained 2026-04-10 failure, which ran while cosmos/example#9 had been open since March 31. Select the target branch and check it out first, while the tree is still clean, then transform directly onto it. Nothing is stashed and nothing is merged, so there is no conflict to hit. Replaces the separate change-detection step with a `git diff --cached --quiet` guard so an up-to-date branch exits cleanly instead of creating an empty commit. Also adds a concurrency group, so two overlapping runs cannot derive divergent commits from the same branch revision and race on push, and workflow_dispatch, so a sync PR left stale by a failed run can be brought current without waiting for the next docs edit. Verified against real clones of cosmos/docs and cosmos/example using the stale cosmos/example#9 branch: with the transform output overlapping lines that branch already changed, the old sequence produces "CONFLICT (content): Merge conflict in docs/02-quickstart.md" and exits 1, while the new sequence exits 0 and stages the correct refresh. --- .github/workflows/docs-sync-to-example.yml | 111 ++++++++++++--------- 1 file changed, 65 insertions(+), 46 deletions(-) diff --git a/.github/workflows/docs-sync-to-example.yml b/.github/workflows/docs-sync-to-example.yml index 368852df..bb3f084d 100644 --- a/.github/workflows/docs-sync-to-example.yml +++ b/.github/workflows/docs-sync-to-example.yml @@ -11,6 +11,19 @@ on: - main paths: - "sdk/next/tutorials/example/**" + # Manual trigger. The push trigger only fires on tutorial .mdx changes, so a + # workflow-only fix cannot re-run the sync, and a sync PR left stale by a + # failed run has no way to catch up until the next docs edit. + workflow_dispatch: + +# Serialize runs. Two overlapping runs would each check out the same revision of +# the open sync branch, commit divergently, and the loser's push would be +# rejected as non-fast-forward, silently dropping its update. Queue instead of +# cancelling: the transform is deterministic from the source docs, so the last +# run to finish always produces the correct final state. +concurrency: + group: docs-sync-to-example + cancel-in-progress: false jobs: sync: @@ -37,6 +50,41 @@ jobs: path: cosmos-example persist-credentials: false + # Select the target branch BEFORE transforming. If an open sync PR exists we + # check its branch out while the tree is still clean, so the transform writes + # directly onto that branch. Switching branches after the transform would + # either abort ("local changes would be overwritten") or, via stash, conflict + # against changes the branch already carries and exit 1 under bash -e. + - name: Select target branch + id: target + env: + GH_TOKEN: ${{ secrets.EXAMPLE_REPO_TOKEN }} + run: | + cd cosmos-example + + EXISTING=$(gh pr list \ + --repo cosmos/example \ + --label "docs-sync" \ + --state open \ + --json number,headRefName \ + --jq '.[0]') + + if [ -n "$EXISTING" ]; then + PR_NUMBER=$(echo "$EXISTING" | jq -r '.number') + BRANCH=$(echo "$EXISTING" | jq -r '.headRefName') + git fetch origin "refs/heads/${BRANCH}:refs/remotes/origin/${BRANCH}" + git checkout -B "$BRANCH" "origin/${BRANCH}" + echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT" + echo "Reusing open sync PR #$PR_NUMBER on branch $BRANCH" + else + BRANCH="docs-sync/from-docs-$(date +%Y%m%d-%H%M%S)" + git checkout -b "$BRANCH" + echo "pr_number=" >> "$GITHUB_OUTPUT" + echo "No open sync PR, will create branch $BRANCH" + fi + + echo "branch=$BRANCH" >> "$GITHUB_OUTPUT" + - name: Transform Mintlify → example repo format run: | python3 scripts/docs-sync/transform.py \ @@ -44,18 +92,11 @@ jobs: --input sdk/next/tutorials/example/ \ --output-dir cosmos-example/docs/ - - name: Check for changes - id: diff - run: | - cd cosmos-example - [ -n "$(git status --porcelain docs/)" ] \ - && echo "changed=true" >> "$GITHUB_OUTPUT" \ - || echo "changed=false" >> "$GITHUB_OUTPUT" - - - name: Open or update PR on cosmos/example - if: steps.diff.outputs.changed == 'true' + - name: Commit, then open or update the PR on cosmos/example env: GH_TOKEN: ${{ secrets.EXAMPLE_REPO_TOKEN }} + BRANCH: ${{ steps.target.outputs.branch }} + PR_NUMBER: ${{ steps.target.outputs.pr_number }} run: | cd cosmos-example @@ -63,50 +104,28 @@ jobs: git config user.email "docs-sync[bot]@users.noreply.github.com" git remote set-url origin "https://x-access-token:${{ secrets.EXAMPLE_REPO_TOKEN }}@github.com/cosmos/example.git" - # Check for an existing open sync PR - EXISTING=$(gh pr list \ - --repo cosmos/example \ - --label "docs-sync" \ - --state open \ - --json number,headRefName \ - --jq '.[0]') + git add docs/ - if [ -n "$EXISTING" ]; then - PR_NUMBER=$(echo "$EXISTING" | jq -r '.number') - BRANCH=$(echo "$EXISTING" | jq -r '.headRefName') + if git diff --cached --quiet; then + echo "No doc changes to sync, nothing to commit." + exit 0 + fi - # Stash the transform output before switching branches, - # then restore it on top of the existing sync branch. - git stash - git fetch origin "$BRANCH" - git checkout "$BRANCH" - git stash pop - git add docs/ - - if git diff --cached --quiet; then - echo "Existing sync branch is already up to date — nothing to commit." - else - git commit -m "docs: sync example tutorials from cosmos/docs [docs-sync]" - git push origin "$BRANCH" - - gh pr comment "$PR_NUMBER" \ - --repo cosmos/example \ - --body "Sync updated: cosmos/docs was updated before this PR merged. Branch has been refreshed — please re-review." - echo "Updated existing PR #$PR_NUMBER" - fi + git commit -m "docs: sync example tutorials from cosmos/docs [docs-sync]" + git push origin "HEAD:refs/heads/${BRANCH}" + if [ -n "$PR_NUMBER" ]; then + gh pr comment "$PR_NUMBER" \ + --repo cosmos/example \ + --body "Sync updated: cosmos/docs was updated before this PR merged. Branch has been refreshed, please re-review." + echo "Updated existing PR #$PR_NUMBER" else - BRANCH="docs-sync/from-docs-$(date +%Y%m%d-%H%M%S)" - git checkout -b "$BRANCH" - git add docs/ - git commit -m "docs: sync example tutorials from cosmos/docs [docs-sync]" - git push origin "$BRANCH" - gh pr create \ --repo cosmos/example \ --head "$BRANCH" \ --base main \ --title "docs: sync example tutorials from cosmos/docs" \ --label "docs-sync" \ - --body "Auto-synced from cosmos/docs. Transforms sdk/next/tutorials/example/*.mdx to docs/*.md. Do not edit docs/ files directly in cosmos/example — edit the source and let the sync bot update them." + --body "Auto-synced from cosmos/docs. Transforms sdk/next/tutorials/example/*.mdx to docs/*.md. Do not edit docs/ files directly in cosmos/example, edit the source and let the sync bot update them." + echo "Opened a new sync PR on branch $BRANCH" fi