Claude was resolving merge conflicts but stopping before running tests, committing, pushing, or creating the PR. Add explicit instruction that the job is not done until the branch is pushed and PR is created.
411 lines
18 KiB
YAML
411 lines
18 KiB
YAML
# Generated by KCL v1.43.2 - DO NOT EDIT
|
|
# Source: .forgejo/kcl-ci/main.k
|
|
# kcl-ci local: ~/stuff/gitrepos/wxs/actions/kcl-ci
|
|
# kcl-ci remote: https://git.wxs.ro/actions/kcl-ci
|
|
|
|
name: Upstream Sync Check
|
|
'on':
|
|
schedule:
|
|
- cron: '0 8 * * 1'
|
|
workflow_dispatch: {}
|
|
env:
|
|
BUN_VERSION: '1.2.21'
|
|
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 }}
|
|
issue_number: ${{ steps.sync-issue.outputs.issue_number }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: https://${{ secrets.READ_REPO_TOKEN }}@git.wxs.ro/xternal/checkout@v6.0.2
|
|
with:
|
|
fetch-depth: '0'
|
|
- name: Configure upstream remote
|
|
run: |-
|
|
if ! git remote get-url upstream &> /dev/null; then
|
|
git remote add upstream https://github.com/anthropics/claude-code-action.git
|
|
fi
|
|
- name: Fetch upstream
|
|
run: git fetch upstream main --quiet
|
|
- name: Compare with upstream
|
|
id: compare
|
|
run: |-
|
|
BEHIND_COUNT=$(git rev-list main..upstream/main --count)
|
|
AHEAD_COUNT=$(git rev-list upstream/main..main --count)
|
|
|
|
echo "behind=$BEHIND_COUNT" >> "$GITHUB_OUTPUT"
|
|
echo "ahead=$AHEAD_COUNT" >> "$GITHUB_OUTPUT"
|
|
|
|
echo "Our main is $BEHIND_COUNT commits behind and $AHEAD_COUNT commits ahead of upstream"
|
|
|
|
if [ "$BEHIND_COUNT" -gt 0 ]; then
|
|
echo "needs_sync=true" >> "$GITHUB_OUTPUT"
|
|
|
|
echo "--- New upstream commits ---"
|
|
git log main..upstream/main --oneline | head -30
|
|
|
|
echo ""
|
|
echo "--- Files changed ---"
|
|
DIFF_STAT=$(git diff --stat main...upstream/main)
|
|
echo "$DIFF_STAT"
|
|
|
|
{
|
|
echo "diff_stat<<DIFF_EOF"
|
|
echo "$DIFF_STAT"
|
|
echo "DIFF_EOF"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
{
|
|
echo "commit_log<<LOG_EOF"
|
|
git log main..upstream/main --oneline | head -30
|
|
echo "LOG_EOF"
|
|
} >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "needs_sync=false" >> "$GITHUB_OUTPUT"
|
|
echo "Already up to date with upstream"
|
|
fi
|
|
- name: Ensure upstream-sync label exists
|
|
if: steps.compare.outputs.needs_sync == 'true'
|
|
id: label
|
|
run: |
|
|
API="${{ env.GITHUB_API_URL }}/repos/${{ github.repository }}"
|
|
AUTH="Authorization: token ${{ secrets.CLAUDE_BOT_TOKEN }}"
|
|
|
|
# Look up the label ID first (Forgejo needs integer IDs, not names)
|
|
LABEL_ID=$(curl -s -H "$AUTH" "$API/labels" | jq -r '[.[] | select(.name == "upstream-sync")] | .[0].id // ""')
|
|
|
|
# Only create label if it doesn't exist yet
|
|
if [ -z "$LABEL_ID" ]; then
|
|
curl -s -o /dev/null \
|
|
-X POST -H "$AUTH" -H "Content-Type: application/json" \
|
|
-d '{"name":"upstream-sync","color":"#0075ca","description":"Upstream sync tracking"}' \
|
|
"$API/labels"
|
|
LABEL_ID=$(curl -s -H "$AUTH" "$API/labels" | jq -r '[.[] | select(.name == "upstream-sync")] | .[0].id // ""')
|
|
echo "Created upstream-sync label with ID: $LABEL_ID"
|
|
else
|
|
echo "Found existing upstream-sync label ID: $LABEL_ID"
|
|
fi
|
|
|
|
echo "label_id=$LABEL_ID" >> "$GITHUB_OUTPUT"
|
|
- name: Check for existing sync issue
|
|
if: steps.compare.outputs.needs_sync == 'true'
|
|
id: existing
|
|
run: |-
|
|
# Forgejo's labels filter requires integer IDs, not names
|
|
RESPONSE=$(curl -s \
|
|
-H "Authorization: token ${{ secrets.CLAUDE_BOT_TOKEN }}" \
|
|
-H "Content-Type: application/json" \
|
|
"${{ env.GITHUB_API_URL }}/repos/${{ github.repository }}/issues?state=open&labels=${{ steps.label.outputs.label_id }}&limit=1")
|
|
|
|
ISSUE_COUNT=$(echo "$RESPONSE" | jq 'length')
|
|
|
|
if [ "$ISSUE_COUNT" -gt 0 ]; then
|
|
ISSUE_NUMBER=$(echo "$RESPONSE" | jq '.[0].number')
|
|
echo "issue_exists=true" >> "$GITHUB_OUTPUT"
|
|
echo "issue_number=$ISSUE_NUMBER" >> "$GITHUB_OUTPUT"
|
|
echo "Found existing sync issue #$ISSUE_NUMBER"
|
|
else
|
|
echo "issue_exists=false" >> "$GITHUB_OUTPUT"
|
|
echo "No existing sync issue found"
|
|
fi
|
|
- name: Create or update sync issue
|
|
if: steps.compare.outputs.needs_sync == 'true'
|
|
id: sync-issue
|
|
run: |-
|
|
API="${{ env.GITHUB_API_URL }}/repos/${{ github.repository }}"
|
|
AUTH="Authorization: token ${{ secrets.CLAUDE_BOT_TOKEN }}"
|
|
LABEL_ID="${{ steps.label.outputs.label_id }}"
|
|
|
|
BEHIND="${{ steps.compare.outputs.behind }}"
|
|
AHEAD="${{ steps.compare.outputs.ahead }}"
|
|
COMMIT_LOG=$(cat <<'LOG_EOF'
|
|
${{ steps.compare.outputs.commit_log }}
|
|
LOG_EOF
|
|
)
|
|
DIFF_STAT=$(cat <<'DIFF_EOF'
|
|
${{ steps.compare.outputs.diff_stat }}
|
|
DIFF_EOF
|
|
)
|
|
|
|
BODY=$(cat <<BODY_EOF
|
|
## Upstream Sync Required
|
|
|
|
Our \`main\` branch is **${BEHIND} commits behind** upstream (\`anthropics/claude-code-action\`).
|
|
We are **${AHEAD} commits ahead** (our Forgejo-specific changes).
|
|
|
|
### New upstream commits (latest ${BEHIND}, max 30 shown)
|
|
|
|
\`\`\`
|
|
${COMMIT_LOG}
|
|
\`\`\`
|
|
|
|
### Files changed
|
|
|
|
\`\`\`
|
|
${DIFF_STAT}
|
|
\`\`\`
|
|
|
|
### How to merge
|
|
|
|
\`\`\`bash
|
|
git fetch upstream main
|
|
git merge upstream/main
|
|
# Resolve any conflicts in the 3-4 modified files (run.ts, tag/index.ts, etc.)
|
|
bun test
|
|
git push origin main
|
|
\`\`\`
|
|
|
|
### Conflict-prone files
|
|
|
|
These files have Forgejo modifications and may conflict:
|
|
- \`src/entrypoints/run.ts\`
|
|
- \`src/modes/tag/index.ts\`
|
|
- \`src/mcp/install-mcp-server.ts\`
|
|
- \`action.yml\`
|
|
|
|
_Auto-generated by upstream-sync workflow on $(date -u +%Y-%m-%d)_
|
|
BODY_EOF
|
|
)
|
|
|
|
TITLE="Upstream sync: ${BEHIND} new commits from anthropics/claude-code-action"
|
|
|
|
if [ "${{ steps.existing.outputs.issue_exists }}" = "true" ]; then
|
|
ISSUE_NUMBER="${{ steps.existing.outputs.issue_number }}"
|
|
curl -s -o /dev/null \
|
|
-X PATCH -H "$AUTH" -H "Content-Type: application/json" \
|
|
-d "$(jq -n --arg title "$TITLE" --arg body "$BODY" '{title: $title, body: $body}')" \
|
|
"$API/issues/${ISSUE_NUMBER}"
|
|
echo "Updated existing issue #${ISSUE_NUMBER}"
|
|
echo "issue_number=${ISSUE_NUMBER}" >> "$GITHUB_OUTPUT"
|
|
else
|
|
# Forgejo expects labels as integer IDs, not objects with names
|
|
RESPONSE=$(curl -s \
|
|
-X POST -H "$AUTH" -H "Content-Type: application/json" \
|
|
-d "$(jq -n --arg title "$TITLE" --arg body "$BODY" --argjson labels "[${LABEL_ID:-0}]" '{title: $title, body: $body, labels: $labels}')" \
|
|
"$API/issues")
|
|
ISSUE_NUMBER=$(echo "$RESPONSE" | jq '.number')
|
|
if [ "$ISSUE_NUMBER" = "null" ] || [ -z "$ISSUE_NUMBER" ]; then
|
|
echo "::error::Failed to create issue. API response:"
|
|
echo "$RESPONSE" | jq '.'
|
|
else
|
|
echo "Created new upstream sync issue #${ISSUE_NUMBER}"
|
|
fi
|
|
echo "issue_number=${ISSUE_NUMBER}" >> "$GITHUB_OUTPUT"
|
|
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: Check for existing sync PR
|
|
id: existing-pr
|
|
run: |-
|
|
# Search for open PRs from chore/upstream-sync-* branches
|
|
RESPONSE=$(curl -s \
|
|
-H "Authorization: token ${{ secrets.CLAUDE_BOT_TOKEN }}" \
|
|
-H "Content-Type: application/json" \
|
|
"${{ env.GITHUB_API_URL }}/repos/${{ 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
|
|
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
|
|
|
|
# Check if existing PR branch is already up to date with upstream
|
|
if [ "${{ steps.existing-pr.outputs.pr_exists }}" = "true" ]; then
|
|
PR_BRANCH="${{ steps.existing-pr.outputs.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)"
|
|
|
|
# Delete stale remote branch if it exists (from a previous run with no PR)
|
|
if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then
|
|
echo "Deleting stale remote branch $BRANCH (no PR references it)"
|
|
git push origin --delete "$BRANCH" || true
|
|
fi
|
|
|
|
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 ==="
|
|
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"
|
|
|
|
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: Install Bun
|
|
run: |-
|
|
if command -v bun &> /dev/null; then
|
|
INSTALLED=$(bun --version)
|
|
if [ "$INSTALLED" = "$BUN_VERSION" ]; then
|
|
echo "Bun $BUN_VERSION already installed"
|
|
else
|
|
echo "Bun $INSTALLED installed, need $BUN_VERSION — reinstalling"
|
|
curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}"
|
|
echo "$HOME/.bun/bin" >> "$GITHUB_PATH"
|
|
fi
|
|
else
|
|
curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}"
|
|
echo "$HOME/.bun/bin" >> "$GITHUB_PATH"
|
|
fi
|
|
if: steps.prepare.outputs.skip != 'true'
|
|
- name: Install dependencies
|
|
run: bun install
|
|
if: steps.prepare.outputs.skip != 'true'
|
|
- name: 'Claude: review merge and create PR'
|
|
if: steps.prepare.outputs.skip != 'true'
|
|
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.
|
|
|
|
## CRITICAL: Do NOT stop early
|
|
You MUST complete ALL steps below before stopping. Resolving conflicts alone is NOT sufficient.
|
|
Your job is NOT done until you have pushed the branch AND created (or updated) the PR.
|
|
If you stop before pushing and creating the PR, the entire sync fails.
|
|
|
|
## 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)
|
|
- Tracking issue: #${{ needs.check-upstream.outputs.issue_number }}
|
|
- Existing PR: ${{ steps.existing-pr.outputs.pr_exists }} (if true, PR #${{ steps.existing-pr.outputs.pr_number }} already exists — update it instead of creating a new one)
|
|
- 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.
|
|
|
|
## IMPORTANT: Command execution rules
|
|
- GITHUB_SERVER_URL=https://github.com is ALREADY exported in your environment. Run commands directly WITHOUT env var prefixes.
|
|
CORRECT: `bun test`
|
|
WRONG: `GITHUB_SERVER_URL=https://github.com bun test` (this will be blocked by permissions)
|
|
- Do NOT use `2>&1` redirections, subshells (`sh -c`), or `export` commands — they will be blocked.
|
|
- For inspecting env vars, use: `bun -e "console.log(process.env.VAR_NAME)"`
|
|
- Available commands: git, bun, bunx, npx, curl, echo, and anything in ./node_modules/.bin/
|
|
|
|
## Known pre-existing test failures
|
|
Some tests in comment-logic.test.ts and create-prompt.test.ts have pre-existing failures related to Forgejo URL patterns.
|
|
These are NOT caused by the upstream merge. When running `bun test`:
|
|
- If only comment-logic.test.ts and/or create-prompt.test.ts fail, that's expected — proceed.
|
|
- If OTHER test files fail that weren't failing before, investigate those as potential merge regressions.
|
|
- Do NOT spend time trying to fix pre-existing failures or trying different env var approaches.
|
|
|
|
## After merge is resolved and reviewed
|
|
1. Run tests: `bun test` — see "Known pre-existing test failures" above
|
|
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. Push the branch, then create or update the PR:
|
|
- If existing PR is "true": PR #${{ steps.existing-pr.outputs.pr_number }} already exists. Just push the branch — the PR updates automatically.
|
|
Optionally update the PR body via PATCH $GITHUB_API_URL/repos/$GITHUB_REPOSITORY/pulls/${{ steps.existing-pr.outputs.pr_number }}
|
|
- 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 ${{ needs.check-upstream.outputs.behind }} upstream commits from anthropics/claude-code-action"
|
|
Body should include:
|
|
- "Closes #${{ needs.check-upstream.outputs.issue_number }}" on its own line (links PR to the tracking issue)
|
|
- 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
|
|
show_full_output: 'true'
|
|
allowed_tools: Bash(git:*),Bash(bun:*),Bash(bunx:*),Bash(npx:*),Bash(./node_modules/.bin/*:*),Bash(curl:*),Bash(echo:*),Bash(cat:*),Read,Write,Edit,MultiEdit,Glob,Grep,WebFetch,WebSearch
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.CLAUDE_BOT_TOKEN }}
|
|
GITHUB_SERVER_URL: https://github.com
|
|
FORK_MODIFIED_FILES: ${{ steps.prepare.outputs.fork_files }}
|
|
AFFECTED_FORK_FILES: ${{ steps.prepare.outputs.affected_files }}
|