feat: add Claude auto-merge to upstream-sync workflow

- New merge-upstream job: creates branch, merges upstream, runs Claude
  agent mode to review, resolve conflicts, run tests, and create a PR
- Dynamic fork impact analysis: computes overlap between fork-modified
  files and upstream-changed files to assess conflict risk
- Claude reviews ALL upstream changes for fork compatibility (renamed
  imports, changed APIs, GitHub-only assumptions) not just conflicts
- Maintainability principle: minimize fork divergence, prefer adapters
- Remove ANTHROPIC_API_KEY (using OAuth token only)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vlad MocanuandClaude Opus 4.6 committed 2026-02-27 11:55:51 +02:00
1 parent 5c014823e0
commit 4954e96cc5
4 files changed
+274 -10

No files matched your search

+121
View File
@@ -12,6 +12,10 @@ jobs:
check-upstream:
name: Check for upstream updates
runs-on: grunt
outputs:
needs_sync: ${{ steps.compare.outputs.needs_sync }}
behind: ${{ steps.compare.outputs.behind }}
ahead: ${{ steps.compare.outputs.ahead }}
steps:
- name: Checkout
uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2
@@ -164,3 +168,120 @@ jobs:
"${{ env.GITHUB_API_URL }}/repos/${{ github.repository }}/issues"
echo "Created new upstream sync issue"
fi
merge-upstream:
name: Auto-merge Upstream
runs-on: grunt
needs:
- check-upstream
if: needs.check-upstream.outputs.needs_sync == 'true'
steps:
- name: Checkout
uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2
with:
fetch-depth: '0'
token: ${{ secrets.CLAUDE_BOT_TOKEN }}
- name: Prepare upstream merge
id: prepare
run: |-
git config user.name "claude"
git config user.email "claude@wxs.ro"
git remote add upstream https://github.com/anthropics/claude-code-action.git
git fetch upstream main --quiet
# 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 ==="
echo "$FORK_FILES"
# Files upstream has changed since we last synced
UPSTREAM_FILES=$(git diff --name-only main...upstream/main | sort -u)
echo ""
echo "=== Upstream-changed files ==="
echo "$UPSTREAM_FILES"
# Intersection: fork files that upstream also changed (conflict risk)
AFFECTED=$(comm -12 <(echo "$FORK_FILES") <(echo "$UPSTREAM_FILES"))
echo ""
echo "=== Affected fork files (overlap) ==="
if [ -z "$AFFECTED" ]; then
echo "(none — low risk merge)"
else
echo "$AFFECTED"
fi
# Export all lists as multiline outputs
echo "fork_files<<EOF" >> "$GITHUB_OUTPUT"
echo "$FORK_FILES" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
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"
else
echo "merge_clean=false" >> "$GITHUB_OUTPUT"
echo "Merge has conflicts — Claude will resolve them"
fi
- name: 'Claude: review merge and create PR'
uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/actions/claude-code-action@main
with:
prompt: |-
You are automating an upstream sync for our Forgejo fork of anthropics/claude-code-action.
## Current state
- Branch: ${{ steps.prepare.outputs.branch }}
- ${{ needs.check-upstream.outputs.behind }} commits behind upstream, ${{ needs.check-upstream.outputs.ahead }} commits ahead (our Forgejo changes)
- Merge was attempted. Status: ${{ steps.prepare.outputs.merge_clean }} (true = clean, false = has conflicts)
## Impact analysis
- $FORK_MODIFIED_FILES — all files our fork has changed vs upstream
- $AFFECTED_FORK_FILES — subset of fork files that upstream ALSO changed (overlap = conflict risk)
- If AFFECTED_FORK_FILES is empty, upstream only touched files we haven't modified — low risk, merge should be clean.
- If AFFECTED_FORK_FILES is non-empty, those files need careful review since both sides changed them.
## If merge has conflicts (merge_clean=false)
Resolve all conflicts. For files in $FORK_MODIFIED_FILES, our fork's changes take PRIORITY — they contain Forgejo-specific adaptations.
## Review ALL upstream changes for fork compatibility
Read CLAUDE.md for context on our Forgejo adapter pattern and platform-specific code.
Even files we haven't modified can break our fork. Check upstream changes for:
- Renamed/moved imports or exports that our Forgejo-specific files depend on
- Changed function signatures, types, or interfaces used by our adapter code
- New assumptions (e.g. GitHub-only APIs) that don't hold on Forgejo
- Removed or restructured code that our platform branches reference
If you find incompatibilities, fix them to maintain Forgejo support.
## Maintainability principle
When fixing incompatibilities, minimize fork divergence to keep future merges easy.
Prefer adapter patterns (separate Forgejo files) over inline modifications to upstream files.
The fewer upstream files we modify, the fewer conflicts in the next sync.
## After merge is resolved and reviewed
1. Run tests: export GITHUB_SERVER_URL=https://github.com && bun test
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
Auth: token $GITHUB_TOKEN
Title: "chore: merge ${{ needs.check-upstream.outputs.behind }} upstream commits from anthropics/claude-code-action"
Body should include:
- Summary of notable upstream changes
- Impact assessment: which fork files (if any) were affected
- List of conflicts resolved (if any)
- If no fork files were affected, note that this is a low-risk merge
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ secrets.CLAUDE_BOT_TOKEN }}
platform: forgejo
env:
GITHUB_TOKEN: ${{ secrets.CLAUDE_BOT_TOKEN }}
FORK_MODIFIED_FILES: ${{ steps.prepare.outputs.fork_files }}
AFFECTED_FORK_FILES: ${{ steps.prepare.outputs.affected_files }}