fix: handle existing sync PRs in upstream-sync workflow

Add idempotency to the merge-upstream job:

1. Check for existing open PR from chore/upstream-sync-* branch
2. If PR exists and branch is up to date with upstream → skip
3. If PR exists but upstream has new commits → checkout existing
   branch, merge new upstream commits, run Claude to review/test
4. If no PR exists → create new branch (existing behavior)

Also update Claude's prompt to handle both create and update PR
cases, so it knows to push to the existing PR branch instead of
creating a duplicate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vlad MocanuandClaude Opus 4.6 committed 2026-02-27 13:15:50 +02:00
1 parent 88e7747d2f
commit b1524d7857
2 files changed
+108 -14

No files matched your search

+56 -8
View File
@@ -233,6 +233,8 @@ _merge_branch_ref = h.gha_steps("prepare", "branch")
_merge_behind_ref = h.gha_needs("check-upstream", "behind")
_merge_ahead_ref = h.gha_needs("check-upstream", "ahead")
_merge_issue_ref = h.gha_needs("check-upstream", "issue_number")
_merge_existing_pr_ref = h.gha_steps("existing-pr", "pr_exists")
_merge_existing_pr_number_ref = h.gha_steps("existing-pr", "pr_number")
_merge_clean_ref = h.gha_steps("prepare", "merge_clean")
_merge_affected_ref = h.gha_steps("prepare", "affected_files")
@@ -243,6 +245,7 @@ You are automating an upstream sync for our Forgejo fork of anthropics/claude-co
- Branch: ${_merge_branch_ref}
- ${_merge_behind_ref} commits behind upstream, ${_merge_ahead_ref} commits ahead (our Forgejo changes)
- Tracking issue: #${_merge_issue_ref}
- Existing PR: ${_merge_existing_pr_ref} (if true, PR #${_merge_existing_pr_number_ref} already exists — update it instead of creating a new one)
- Merge was attempted. Status: ${_merge_clean_ref} (true = clean, false = has conflicts)
## Impact analysis
@@ -273,8 +276,10 @@ The fewer upstream files we modify, the fewer conflicts in the next sync.
2. Run typecheck: bun run typecheck
3. Check formatting: bun run format:check — if issues, fix with bun run format
4. Commit any remaining changes and push the branch
5. Create a PR targeting main via the Forgejo API:
POST ${"$"}GITHUB_API_URL/repos/${"$"}GITHUB_REPOSITORY/pulls
5. Push the branch, then create or update the PR:
- If existing PR is "true": PR #${_merge_existing_pr_number_ref} already exists. Just push the branch — the PR updates automatically.
Optionally update the PR body via PATCH ${"$"}GITHUB_API_URL/repos/${"$"}GITHUB_REPOSITORY/pulls/${_merge_existing_pr_number_ref}
- If existing PR is "false": Create a new PR via POST ${"$"}GITHUB_API_URL/repos/${"$"}GITHUB_REPOSITORY/pulls
Auth: token ${"$"}GITHUB_TOKEN
Title: "chore: merge ${_merge_behind_ref} upstream commits from anthropics/claude-code-action"
Body should include:
@@ -491,6 +496,29 @@ fi"""
token = h.gha_secret("CLAUDE_BOT_TOKEN")
}
}
{
name = "Check for existing sync PR"
id = "existing-pr"
run = """\
# Search for open PRs from chore/upstream-sync-* branches
RESPONSE=${"$"}(curl -s \\
-H "Authorization: token ${h.gha_secret("CLAUDE_BOT_TOKEN")}" \\
-H "Content-Type: application/json" \\
"${h.gha_env("GITHUB_API_URL")}/repos/${h.gha_github("repository")}/pulls?state=open&limit=50")
PR_BRANCH=${"$"}(echo "${"$"}RESPONSE" | jq -r '[.[] | select(.head.ref | startswith("chore/upstream-sync-"))] | .[0].head.ref // ""')
if [ -n "${"$"}PR_BRANCH" ]; then
PR_NUMBER=${"$"}(echo "${"$"}RESPONSE" | jq -r '[.[] | select(.head.ref | startswith("chore/upstream-sync-"))] | .[0].number')
echo "Found existing sync PR #${"$"}PR_NUMBER on branch ${"$"}PR_BRANCH"
echo "pr_exists=true" >> "${"$"}GITHUB_OUTPUT"
echo "pr_branch=${"$"}PR_BRANCH" >> "${"$"}GITHUB_OUTPUT"
echo "pr_number=${"$"}PR_NUMBER" >> "${"$"}GITHUB_OUTPUT"
else
echo "No existing sync PR found"
echo "pr_exists=false" >> "${"$"}GITHUB_OUTPUT"
fi"""
}
{
name = "Prepare upstream merge"
id = "prepare"
@@ -501,6 +529,29 @@ git config user.email "claude@wxs.ro"
git remote add upstream https://github.com/anthropics/claude-code-action.git
git fetch upstream main --quiet
# Check if existing PR branch is already up to date with upstream
if [ "${h.gha_steps("existing-pr", "pr_exists")}" = "true" ]; then
PR_BRANCH="${h.gha_steps("existing-pr", "pr_branch")}"
git checkout "${"$"}PR_BRANCH"
# Check if upstream/main is already merged into the PR branch
BEHIND=${"$"}(git rev-list "${"$"}PR_BRANCH"..upstream/main --count)
if [ "${"$"}BEHIND" -eq 0 ]; then
echo "Existing PR branch ${"$"}PR_BRANCH is already up to date with upstream"
echo "skip=true" >> "${"$"}GITHUB_OUTPUT"
exit 0
fi
echo "Existing PR branch ${"$"}PR_BRANCH is ${"$"}BEHIND commits behind upstream — updating"
BRANCH="${"$"}PR_BRANCH"
else
BRANCH="chore/upstream-sync-${"$"}(date +%Y%m%d)"
git checkout -b "${"$"}BRANCH"
fi
echo "skip=false" >> "${"$"}GITHUB_OUTPUT"
echo "branch=${"$"}BRANCH" >> "${"$"}GITHUB_OUTPUT"
# Files our fork has modified vs upstream
FORK_FILES=${"$"}(git log upstream/main..main --name-only --pretty=format: | sort -u | grep -v '^${"$"}')
echo "=== Fork-modified files ==="
@@ -531,10 +582,6 @@ echo "affected_files<<EOF" >> "${"$"}GITHUB_OUTPUT"
echo "${"$"}{AFFECTED:-(none)}" >> "${"$"}GITHUB_OUTPUT"
echo "EOF" >> "${"$"}GITHUB_OUTPUT"
BRANCH="chore/upstream-sync-${"$"}(date +%Y%m%d)"
git checkout -b "${"$"}BRANCH"
echo "branch=${"$"}BRANCH" >> "${"$"}GITHUB_OUTPUT"
if git merge upstream/main --no-edit; then
echo "merge_clean=true" >> "${"$"}GITHUB_OUTPUT"
echo "Merge completed cleanly"
@@ -543,10 +590,11 @@ else
echo "Merge has conflicts — Claude will resolve them"
fi"""
}
_bun_install()
_bun_deps()
_bun_install() | {"if" = "steps.prepare.outputs.skip != 'true'"}
_bun_deps() | {"if" = "steps.prepare.outputs.skip != 'true'"}
{
name = "Claude: review merge and create PR"
"if" = "steps.prepare.outputs.skip != 'true'"
uses = _claude_action_url
"with" = {
prompt = _merge_prompt