-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Auto-resolve pr_body.md conflicts before Codex runs #685
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| #!/usr/bin/env bash | ||
| # Auto-resolve conflicts in files that should use "ours" merge strategy | ||
| # These files are PR-specific and should keep the PR branch version when conflicts occur | ||
| # | ||
| # Usage: ./auto_resolve_ignored_conflicts.sh <base_branch> | ||
| # | ||
| # This script: | ||
| # 1. Fetches the latest base branch | ||
| # 2. Attempts to merge base into the current branch | ||
| # 3. For known PR-specific files, auto-resolves using --ours | ||
| # 4. Commits the resolution if successful | ||
|
|
||
| set -e | ||
|
|
||
| BASE_BRANCH="${1:-main}" | ||
|
|
||
| # Files that should always keep the PR branch version (configured in .gitattributes with merge=ours) | ||
| # but need manual resolution since the git driver isn't configured | ||
| IGNORED_CONFLICT_FILES=( | ||
| "pr_body.md" | ||
| "ci/autofix/history.json" | ||
| "keepalive-metrics.ndjson" | ||
| "coverage-trend-history.ndjson" | ||
| "metrics-history.ndjson" | ||
| "residual-trend-history.ndjson" | ||
| ) | ||
|
|
||
| echo "=== Auto-Resolve Ignored Conflict Files ===" | ||
| echo "Base branch: $BASE_BRANCH" | ||
|
|
||
| # Fetch latest base | ||
| echo "Fetching origin/$BASE_BRANCH..." | ||
| git fetch origin "$BASE_BRANCH" --quiet | ||
|
|
||
| # Check if we're already up to date | ||
| if git merge-base --is-ancestor "origin/$BASE_BRANCH" HEAD 2>/dev/null; then | ||
| echo "✓ Branch is already up to date with $BASE_BRANCH" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Try to merge - this may create conflicts | ||
| echo "Attempting merge from origin/$BASE_BRANCH..." | ||
| if git merge "origin/$BASE_BRANCH" --no-edit 2>/dev/null; then | ||
| echo "✓ Merge completed without conflicts" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Merge failed - check for conflicts in ignored files | ||
| echo "Merge has conflicts. Checking for auto-resolvable files..." | ||
|
|
||
| RESOLVED_COUNT=0 | ||
| REMAINING_CONFLICTS=() | ||
|
|
||
| # Get list of unmerged files | ||
| while IFS= read -r conflict_file; do | ||
| [ -z "$conflict_file" ] && continue | ||
|
|
||
| SHOULD_AUTO_RESOLVE=false | ||
| for ignored in "${IGNORED_CONFLICT_FILES[@]}"; do | ||
| if [[ "$conflict_file" == "$ignored" || "$conflict_file" == *"/$ignored" ]]; then | ||
| SHOULD_AUTO_RESOLVE=true | ||
| break | ||
| fi | ||
| done | ||
|
|
||
| if $SHOULD_AUTO_RESOLVE; then | ||
| echo " → Auto-resolving: $conflict_file (keeping ours)" | ||
| git checkout --ours "$conflict_file" 2>/dev/null || true | ||
| git add "$conflict_file" 2>/dev/null || true | ||
| ((RESOLVED_COUNT++)) | ||
| else | ||
| REMAINING_CONFLICTS+=("$conflict_file") | ||
| fi | ||
| done < <(git diff --name-only --diff-filter=U 2>/dev/null) | ||
|
|
||
| if [ "$RESOLVED_COUNT" -gt 0 ]; then | ||
| echo "✓ Auto-resolved $RESOLVED_COUNT file(s)" | ||
| fi | ||
|
|
||
| if [ ${#REMAINING_CONFLICTS[@]} -eq 0 ]; then | ||
| # All conflicts resolved - commit | ||
| echo "All conflicts resolved. Committing..." | ||
| git commit -m "fix: auto-resolve PR-specific file conflicts with $BASE_BRANCH | ||
|
|
||
| Files resolved using --ours strategy: | ||
| $(for f in "${IGNORED_CONFLICT_FILES[@]}"; do echo "- $f"; done) | ||
|
|
||
| These files are PR-specific and should not inherit content from the base branch." | ||
| echo "✓ Merge conflict resolution committed" | ||
| exit 0 | ||
|
Comment on lines
+80
to
+90
|
||
| else | ||
| echo "" | ||
| echo "⚠ Remaining conflicts require manual resolution:" | ||
| for f in "${REMAINING_CONFLICTS[@]}"; do | ||
| echo " - $f" | ||
| done | ||
| echo "" | ||
| echo "Run 'git status' to see conflict details." | ||
| exit 1 | ||
| fi | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -314,14 +314,109 @@ jobs: | |||||||||||||||||
| }; | ||||||||||||||||||
| await markAgentRunning({ github, context, core, inputs }); | ||||||||||||||||||
|
|
||||||||||||||||||
| # Auto-resolve trivial conflicts (pr_body.md, etc.) before running Codex | ||||||||||||||||||
| resolve-trivial-conflicts: | ||||||||||||||||||
| name: Auto-resolve PR-specific files | ||||||||||||||||||
| needs: | ||||||||||||||||||
| - evaluate | ||||||||||||||||||
| - preflight | ||||||||||||||||||
| if: needs.evaluate.outputs.action == 'conflict' | ||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||
| outputs: | ||||||||||||||||||
| resolved: ${{ steps.resolve.outputs.resolved }} | ||||||||||||||||||
| remaining_conflicts: ${{ steps.resolve.outputs.remaining_conflicts }} | ||||||||||||||||||
| steps: | ||||||||||||||||||
| - name: Generate GitHub App token | ||||||||||||||||||
| id: app-token | ||||||||||||||||||
| if: ${{ vars.WORKFLOWS_APP_ID != '' }} | ||||||||||||||||||
| uses: actions/create-github-app-token@v2 | ||||||||||||||||||
| with: | ||||||||||||||||||
| app-id: ${{ vars.WORKFLOWS_APP_ID }} | ||||||||||||||||||
|
Comment on lines
+331
to
+334
|
||||||||||||||||||
| if: ${{ vars.WORKFLOWS_APP_ID != '' }} | |
| uses: actions/create-github-app-token@v2 | |
| with: | |
| app-id: ${{ vars.WORKFLOWS_APP_ID }} | |
| if: ${{ secrets.WORKFLOWS_APP_ID != '' }} | |
| uses: actions/create-github-app-token@v2 | |
| with: | |
| app-id: ${{ secrets.WORKFLOWS_APP_ID }} |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file list here contains only 3 files, but the standalone script at .github/scripts/auto_resolve_ignored_conflicts.sh contains 6 files (including "coverage-trend-history.ndjson", "metrics-history.ndjson", and "residual-trend-history.ndjson"). These lists should be consistent to ensure the same behavior across different usage contexts.
| IGNORED_FILES=("pr_body.md" "ci/autofix/history.json" "keepalive-metrics.ndjson") | |
| IGNORED_FILES=("pr_body.md" "ci/autofix/history.json" "keepalive-metrics.ndjson" "coverage-trend-history.ndjson" "metrics-history.ndjson" "residual-trend-history.ndjson") |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the merge fails but no unmerged files are detected by git diff --name-only --diff-filter=U (REMAINING=0, RESOLVED=0), neither the commit branch (line 397) nor the abort branch (line 401) executes. This leaves the repository in a conflicted merge state without cleanup. Consider adding an else clause to handle this edge case, likely by aborting the merge with git merge --abort.
| git merge --abort || true | |
| git merge --abort || true | |
| else | |
| echo "⚠ Merge did not complete successfully, but no conflicts were detected (resolved=$RESOLVED, remaining=$REMAINING). Aborting merge to clean up state." | |
| git merge --abort || true |
Copilot
AI
Jan 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition only checks for 'skipped' result, not 'failure'. If the resolve-trivial-conflicts job fails (e.g., git push fails at line 399), the run-codex job will not execute because the condition doesn't account for the 'failure' result. Consider updating the condition to also handle job failures, for example: needs.resolve-trivial-conflicts.result == 'skipped' || needs.resolve-trivial-conflicts.result == 'failure' || needs.resolve-trivial-conflicts.outputs.remaining_conflicts != '0'
| (needs.resolve-trivial-conflicts.result == 'skipped' || | |
| (needs.resolve-trivial-conflicts.result == 'skipped' || | |
| needs.resolve-trivial-conflicts.result == 'failure' || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The commit message lists all files in the IGNORED_CONFLICT_FILES array, regardless of which files actually had conflicts and were resolved. This could be misleading. Consider only listing the files that were actually resolved in this specific merge, similar to how the workflow inline script does it.