Skip to content

Fall back to original base commit when git am --3way fails due to merge conflicts#23132

Merged
dsyme merged 7 commits intomainfrom
copilot/fix-pr-creation-merge-conflicts
Mar 26, 2026
Merged

Fall back to original base commit when git am --3way fails due to merge conflicts#23132
dsyme merged 7 commits intomainfrom
copilot/fix-pr-creation-merge-conflicts

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 26, 2026

PR creation was failing outright when git am --3way couldn't resolve conflicts caused by intervening commits on the target branch. Instead of failing, the PR should still be created at the original base commit so GitHub can surface the conflicts for manual resolution.

Changes

  • create_pull_request.cjs: On git am --3way failure, instead of immediately returning an error:

    1. Abort the failed git am
    2. Extract the first patch commit SHA from the From <sha1> header in the patch file
    3. Resolve the parent commit (git rev-parse <sha>^) — this is the original base the patch was generated from
    4. Verify the parent exists locally (graceful no-op for cross-repo cases where the SHA is absent)
    5. Recreate the PR branch at that original base commit and apply the patch without --3way
    6. Continue with push + PR creation — GitHub will show the merge conflicts for manual resolution
    7. Fall through to the original error only if the fallback also fails
  • create_pull_request.test.cjs: Three new tests covering the fallback path — success via fallback, double failure, and cross-repo SHA unavailable.

  • docs/src/content/docs/reference/safe-outputs-pull-requests.md: Added a "How PR creation works" section explaining the end-to-end git patch mechanism, what happens when the target branch has changed (clean merge vs. conflict fallback), the cross-repo limitation, and how .github/workflows changes are handled via the protected files policy.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 2 commits March 26, 2026 15:00
When git am --3way fails due to merge conflicts (intervening commits to
the target branch), fall back to creating the PR branch at the original
base commit. The patch is then applied cleanly at that earlier commit,
and the resulting PR will show merge conflicts in GitHub for manual
resolution.

This implements Option 2 (try --3way first) with fallback to Option 1
(apply at original base commit) as discussed in the issue.

The original base commit is found by:
1. Parsing the "From <sha1>" header of the first patch in the series
2. Resolving the parent commit with git rev-parse <sha>^
3. Verifying it exists locally (fails gracefully for cross-repo cases)

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/0f83490d-837a-47a6-bd2c-e11e6563085e

Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com>
- Rename PATCH_COMMIT_SHA/PARENT_SHA to MOCK_PATCH_COMMIT_SHA/MOCK_PARENT_COMMIT_SHA
- Use single template literal instead of string concatenation in warning
- Replace fragile negative lookahead regex with explicit /^git am [^-]/ pattern

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/0f83490d-837a-47a6-bd2c-e11e6563085e

Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix PR creation failure due to merge conflicts Fall back to original base commit when git am --3way fails due to merge conflicts Mar 26, 2026
Copilot AI requested a review from dsyme March 26, 2026 15:04
}

return { success: false, error: "Failed to apply patch" };
// Abort the failed git am before attempting any fallback
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Add brief docs to the Safe Outputs (Pull Requests) doc page about how, at a high level, gh-aw agentic workflows take the change generated by the coding agent and create or update a pull request. Cover what happens if the target branch has changed, if there are conflicts, if there are files that are protected, if there are .github/workflows files being changed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a "How PR creation works" section to the Safe Outputs (Pull Requests) page in commit dfece51. It covers:

  • The end-to-end mechanism: git format-patchgit am --3way → push → GitHub API creates PR
  • What happens when the target branch has changed — clean merge succeeds normally; unresolvable conflicts fall back to applying the patch at the original base commit so GitHub surfaces the conflicts for manual resolution
  • The cross-repo caveat (original base commit fallback requires the commit to be present locally)
  • What happens with .github/workflows changes — .github/ is protected by default and either hard-blocks or falls back to a review issue depending on the configured protected-files policy

@dsyme
Copy link
Copy Markdown
Collaborator

dsyme commented Mar 26, 2026

Adding some docs

Explains the git patch mechanism, what happens when the target branch
has changed (no conflicts vs merge conflicts with fallback), and what
happens when .github/workflows files are modified (protected files).

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/5a1f5ac0-7653-4770-8fb5-92b991bccb60

Co-authored-by: dsyme <7204669+dsyme@users.noreply.github.com>
@dsyme dsyme marked this pull request as ready for review March 26, 2026 17:20
Copilot AI review requested due to automatic review settings March 26, 2026 17:20
Copilot AI requested a review from dsyme March 26, 2026 17:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves safe-output PR creation resiliency by attempting to still create a pull request even when git am --3way fails due to merge conflicts, so GitHub can surface conflicts for manual resolution.

Changes:

  • Add a fallback path in create_pull_request.cjs after git am --3way failure: abort git am, attempt to recreate the PR branch at an “original base” commit, and re-apply the patch without --3way.
  • Add new tests intended to cover fallback success, double-failure, and cross-repo base-SHA-unavailable scenarios.
  • Document the PR creation flow and the intended conflict-handling fallback behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
docs/src/content/docs/reference/safe-outputs-pull-requests.md Documents patch application flow and the intended fallback behavior when the base branch has advanced.
actions/setup/js/create_pull_request.cjs Implements the fallback logic after git am --3way failure.
actions/setup/js/create_pull_request.test.cjs Adds tests covering the new fallback path.
Comments suppressed due to low confidence (2)

actions/setup/js/create_pull_request.cjs:775

  • The fallback path also interpolates patchFilePath into git am ${patchFilePath}. For the same reasons as the primary apply path, prefer calling exec.exec("git", ["am", patchFilePath]) so the patch path can’t be misparsed by shell tokenization.
            // Apply the patch without --3way; we are on the correct base so it should apply cleanly
            await exec.exec(`git am ${patchFilePath}`);
            core.info("Patch applied successfully at original base commit");

docs/src/content/docs/reference/safe-outputs-pull-requests.md:29

  • The doc states the job can “fall back to applying the patch at the commit the agent originally branched from”. As implemented, the fallback currently tries to infer that base from the patch’s From <sha> header, which won’t be resolvable in the target checkout (the commit object doesn’t exist there). Once the implementation is corrected (e.g., by recording the base commit alongside the patch or embedding a base-commit: header), please ensure this section reflects the actual source of truth for the original base commit.
    max: 3                        # max PRs per run (default: 1)
    expires: 14                   # auto-close after 14 days (same-repo only)
    if-no-changes: "warn"         # "warn" (default), "error", or "ignore"
    target-repo: "owner/repo"     # cross-repository
    allowed-repos: ["org/repo1", "org/repo2"]  # additional allowed repositories

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1111 to +1115
getExecOutput: vi.fn().mockImplementation((cmd, args) => {
// Return the parent SHA for git rev-parse calls
if (cmd === "git" && Array.isArray(args) && args[0] === "rev-parse") {
return Promise.resolve({ exitCode: 0, stdout: `${MOCK_PARENT_COMMIT_SHA}\n`, stderr: "" });
}
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests assume the SHA from the patch From <sha> header can be used with git rev-parse <sha>^ in the PR-creation job. In practice that <sha> refers to the agent’s new commit object, which won’t exist in the clean target checkout, so rev-parse will fail and this test would not represent real behavior. Once the implementation records the base commit explicitly (or parses a base-commit: header), adjust the mocks/assertions to match that mechanism rather than mocking rev-parse success for an unknown commit.

Suggested change
getExecOutput: vi.fn().mockImplementation((cmd, args) => {
// Return the parent SHA for git rev-parse calls
if (cmd === "git" && Array.isArray(args) && args[0] === "rev-parse") {
return Promise.resolve({ exitCode: 0, stdout: `${MOCK_PARENT_COMMIT_SHA}\n`, stderr: "" });
}
getExecOutput: vi.fn().mockImplementation((_cmd, _args) => {
// Return a generic successful result for commands that use getExecOutput.
// We deliberately do not special-case "git rev-parse" here, to avoid
// assuming that the patch's "From <sha>" commit exists in the clean checkout.

Copilot uses AI. Check for mistakes.
dsyme and others added 2 commits March 26, 2026 17:27
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
The fallback in create_pull_request.cjs tried to derive the base commit
by parsing the From <sha> header from git format-patch output and running
git rev-parse <sha>^. That SHA is the agent's new commit which doesn't
exist in the clean target checkout, so rev-parse always fails and the
fallback never activates.

Fix: record the resolved base commit SHA explicitly in generate_git_patch.cjs
(where it's known), pass it through the safe outputs entry as base_commit,
and use it directly in the create_pull_request fallback path.
@dsyme dsyme merged commit 92f66ac into main Mar 26, 2026
55 checks passed
@dsyme dsyme deleted the copilot/fix-pr-creation-merge-conflicts branch March 26, 2026 18:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PR creation fails if there are merge conflicts with intervening updates to target branch

3 participants