Skip to content

fixes #31 Multi-conflict chains leave orphaned merge-forward branches#32

Merged
mattsgarlata merged 1 commit intomainfrom
issue-31-multi-conflict-chain-cleanup
Feb 25, 2026
Merged

fixes #31 Multi-conflict chains leave orphaned merge-forward branches#32
mattsgarlata merged 1 commit intomainfrom
issue-31-multi-conflict-chain-cleanup

Conversation

@mattsgarlata
Copy link
Copy Markdown
Member

Closes #31

When a merge chain hit conflicts at multiple points (e.g., both release-5.8.0 and main), each conflict resolution PR created merge-forward branches under its own PR number. Cleanup only searched for the latest PR's branches, leaving earlier ones orphaned and branch-here un-advanced.

Changes

  • Added extractOriginalPRNumber to branch-name-utils.js — shared function that traces back to the original PR number from merge-forward base refs or merge-conflicts head refs
  • AutoMerger.originalPRNumber getter delegates to the shared function
  • createMergeForwardBranchName and createMergeConflictsBranchName use originalPRNumber, keeping the entire chain under one PR number
  • Removed duplicate BranchMaintainer.determineOriginalPRNumber() (DRY)
  • updateTargetBranches simplified to use the same getter

Made with Cursor

When a merge chain hit conflicts at multiple points, each conflict
resolution PR created merge-forward branches under its own PR number.
Cleanup only searched for the latest PR's branches, leaving earlier
ones orphaned and branch-here un-advanced.

Added extractOriginalPRNumber to branch-name-utils.js so both
AutoMerger and BranchMaintainer trace back to the original PR
number. createMergeForwardBranchName and createMergeConflictsBranchName
now use this, keeping the entire chain discoverable under one PR
number.

Co-authored-by: Cursor <cursoragent@cursor.com>
@mattsgarlata mattsgarlata marked this pull request as ready for review February 25, 2026 11:58
@mattsgarlata
Copy link
Copy Markdown
Member Author

@coderabbitai full review

@mattsgarlata
Copy link
Copy Markdown
Member Author

Hmm, Coderabbit doesn't seem to be working. I tried to go configure it, but the config screen looks messed up. I'm guessing it's an issue on their end so I'll just merge this in.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 25, 2026

✅ Actions performed

Full review triggered.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 25, 2026

Walkthrough

Adds extractOriginalPRNumber and an originalPRNumber getter, switching AutoMerger and BranchMaintainer to derive and use the original PR number for branch naming and cleanup; updates tests to validate propagation and branch-name behavior across conflict-resolution chains.

Changes

Cohort / File(s) Summary
Branch Name Utilities
src/branch-name-utils.js
Added extractOriginalPRNumber({ baseRef, headRef, prNumber }) that derives the original PR by checking merge-forward baseRef, then merge-conflicts headRef, falling back to prNumber. Export updated to include the new function.
AutoMerger updates
src/automerger.js
Added public getter originalPRNumber (uses extractOriginalPRNumber) and replaced usages of prNumber/extractPRFromMergeForward with originalPRNumber in branch naming and target-branch computation.
BranchMaintainer updates
src/branch-maintainer.js
Removed determineOriginalPRNumber() and replaced prior extraction logic with extractOriginalPRNumber in cleanupMergeForwardBranches to resolve the original PR number for cleanup.
Tests
test/auto-merge-action-test.js, test/branch-name-utils-test.js
Added tests verifying originalPRNumber propagation for normal and conflict-resolution chains, and that merge-forward/merge-conflicts branch names use the original PR number; added unit tests for extractOriginalPRNumber behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing issue #31 related to orphaned merge-forward branches in multi-conflict chains.
Description check ✅ Passed The description clearly explains the problem, the solution, and lists the specific code changes made to address issue #31.
Linked Issues check ✅ Passed The PR successfully implements all requirements from issue #31: introduces extractOriginalPRNumber for tracing original PR numbers, updates AutoMerger and BranchMaintainer to use it, and ensures entire chains use consistent PR numbering for cleanup discovery.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing issue #31; no out-of-scope modifications detected. The refactoring removes duplicate code while centralizing logic as intended.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch issue-31-multi-conflict-chain-cleanup

Comment @coderabbitai help to get the list of available commands and usage tips.

@mattsgarlata
Copy link
Copy Markdown
Member Author

It looks like CodeRabbit was just slower to respond than normal

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/automerger.js`:
- Around line 118-124: The getter originalPRNumber uses
this.pullRequest.head?.ref which still throws if this.pullRequest is undefined;
update the reference to this.pullRequest?.head?.ref to fully guard access (in
the get originalPRNumber method that calls extractOriginalPRNumber) so that
extractOriginalPRNumber receives undefined safely when pullRequest is omitted
from the constructor.

In `@test/branch-name-utils-test.js`:
- Around line 93-101: The test "prefers base over head when both present"
currently uses baseRef and headRef that both resolve to PR 100 so it doesn't
verify priority; update the headRef (or baseRef) so the two refs encode
different PR numbers (e.g., make headRef resolve to 200 while leaving baseRef as
100) and assert extractOriginalPRNumber(...) returns the baseRef's PR ('100') to
prove baseRef wins; ensure the test still calls extractOriginalPRNumber with the
same keys (baseRef, headRef, prNumber) so you only change the ref string
content.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0fa592a and 10b7bf0.

📒 Files selected for processing (5)
  • src/automerger.js
  • src/branch-maintainer.js
  • src/branch-name-utils.js
  • test/auto-merge-action-test.js
  • test/branch-name-utils-test.js

Comment on lines +118 to +124
get originalPRNumber() {
return extractOriginalPRNumber({
baseRef: this.baseBranch,
headRef: this.pullRequest.head?.ref,
prNumber: this.prNumber
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check TestAutoMerger and pullRequest initialization
rg -n "class TestAutoMerger" test/ -A 20 | head -50

Repository: SpiderStrategies/merge-bot

Length of output: 2781


🏁 Script executed:

# Find where pullRequest is initialized in test helpers
rg -n "pullRequest" test/test-helpers.js -B 3 -A 3

Repository: SpiderStrategies/merge-bot

Length of output: 581


🏁 Script executed:

# Check tests calling originalPRNumber
rg -n "originalPRNumber" test/ -B 5 -A 2

Repository: SpiderStrategies/merge-bot

Length of output: 1720


🏁 Script executed:

# Inspect the actual constructor in src/automerger.js
head -120 src/automerger.js | tail -60

Repository: SpiderStrategies/merge-bot

Length of output: 1844


🏁 Script executed:

# Get complete TestAutoMerger constructor
sed -n '167,210p' test/test-helpers.js

Repository: SpiderStrategies/merge-bot

Length of output: 1377


🏁 Script executed:

# Get AutoMerger constructor to see how pullRequest is assigned
sed -n '1,100p' src/automerger.js | grep -A 40 "constructor"

Repository: SpiderStrategies/merge-bot

Length of output: 960


🏁 Script executed:

# Check if AutoMerger is ever instantiated directly in production code without pullRequest
rg -n "new AutoMerger" src/ --type js

Repository: SpiderStrategies/merge-bot

Length of output: 126


🏁 Script executed:

# Check how AutoMerger is instantiated in the action
rg -n "new AutoMerger\|extends AutoMerger" src/ test/ --type js

Repository: SpiderStrategies/merge-bot

Length of output: 52


🏁 Script executed:

# Check how AutoMerger is instantiated in merge-bot.js
sed -n '20,40p' src/merge-bot.js

Repository: SpiderStrategies/merge-bot

Length of output: 456


🏁 Script executed:

# Verify what object is passed to AutoMerger constructor
sed -n '25,50p' src/merge-bot.js

Repository: SpiderStrategies/merge-bot

Length of output: 566


this.pullRequest.head?.ref lacks protection if pullRequest is undefined.

The optional chaining on .head?.ref is insufficient; if this.pullRequest itself is undefined (e.g., when pullRequest is omitted from the constructor), it throws a TypeError before the chain evaluates. Use this.pullRequest?.head?.ref for complete protection.

Proposed fix
 get originalPRNumber() {
 	return extractOriginalPRNumber({
 		baseRef: this.baseBranch,
-		headRef: this.pullRequest.head?.ref,
+		headRef: this.pullRequest?.head?.ref,
 		prNumber: this.prNumber
 	})
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
get originalPRNumber() {
return extractOriginalPRNumber({
baseRef: this.baseBranch,
headRef: this.pullRequest.head?.ref,
prNumber: this.prNumber
})
}
get originalPRNumber() {
return extractOriginalPRNumber({
baseRef: this.baseBranch,
headRef: this.pullRequest?.head?.ref,
prNumber: this.prNumber
})
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/automerger.js` around lines 118 - 124, The getter originalPRNumber uses
this.pullRequest.head?.ref which still throws if this.pullRequest is undefined;
update the reference to this.pullRequest?.head?.ref to fully guard access (in
the get originalPRNumber method that calls extractOriginalPRNumber) so that
extractOriginalPRNumber receives undefined safely when pullRequest is omitted
from the constructor.

Comment on lines +93 to +101
t.test('prefers base over head when both present',
async t => {
t.equal(extractOriginalPRNumber({
baseRef: 'merge-forward-pr-100-main',
headRef: 'merge-conflicts-200-pr-100' +
'-release-5.8.0-to-main',
prNumber: 999
}), '100')
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

"prefers base over head" test doesn't actually verify priority.

Both baseRef (merge-forward-pr-100-main) and headRef (merge-conflicts-200-pr-100-release-5.8.0-to-main) resolve to the same PR number ('100'), so the assertion passes regardless of evaluation order. To truly verify that baseRef wins when both are present, the two refs must encode different PR numbers.

♻️ Proposed fix
 t.test('prefers base over head when both present',
 		async t => {
 	t.equal(extractOriginalPRNumber({
 		baseRef: 'merge-forward-pr-100-main',
-		headRef: 'merge-conflicts-200-pr-100' +
-			'-release-5.8.0-to-main',
+		headRef: 'merge-conflicts-200-pr-999' +
+			'-release-5.8.0-to-main',
 		prNumber: 999
-	}), '100')
+	}), '100',
+	'baseRef-derived number (100) should win over headRef-derived number (999)')
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
t.test('prefers base over head when both present',
async t => {
t.equal(extractOriginalPRNumber({
baseRef: 'merge-forward-pr-100-main',
headRef: 'merge-conflicts-200-pr-100' +
'-release-5.8.0-to-main',
prNumber: 999
}), '100')
})
t.test('prefers base over head when both present',
async t => {
t.equal(extractOriginalPRNumber({
baseRef: 'merge-forward-pr-100-main',
headRef: 'merge-conflicts-200-pr-999' +
'-release-5.8.0-to-main',
prNumber: 999
}), '100',
'baseRef-derived number (100) should win over headRef-derived number (999)')
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/branch-name-utils-test.js` around lines 93 - 101, The test "prefers base
over head when both present" currently uses baseRef and headRef that both
resolve to PR 100 so it doesn't verify priority; update the headRef (or baseRef)
so the two refs encode different PR numbers (e.g., make headRef resolve to 200
while leaving baseRef as 100) and assert extractOriginalPRNumber(...) returns
the baseRef's PR ('100') to prove baseRef wins; ensure the test still calls
extractOriginalPRNumber with the same keys (baseRef, headRef, prNumber) so you
only change the ref string content.

@mattsgarlata mattsgarlata merged commit 0caa950 into main Feb 25, 2026
2 checks passed
@mattsgarlata mattsgarlata deleted the issue-31-multi-conflict-chain-cleanup branch February 25, 2026 12:14
mattsgarlata added a commit that referenced this pull request Feb 25, 2026
Add optional chaining on this.pullRequest in originalPRNumber getter
to guard against undefined. Fix "prefers base over head" test to use
different PR numbers so it actually verifies evaluation priority.

Co-authored-by: Cursor <cursoragent@cursor.com>
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.

Multi-conflict chains leave orphaned merge-forward branches

1 participant