fix(engine): short-circuit zero-commits-ahead branch before AI-merge clean-room churn#1920
Conversation
…clean-room churn An AI-merge land of a branch with zero commits ahead of the integration tip (the shape a coding agent that produced no commits leaves behind) builds a clean-room worktree and runs a dependency install before reaching the empty outcome via mergeAndReview. On a non-workspace land the dep install throws hard, so the merge is transient-retried to exhaustion (3/3) and the card is terminally parked failed. Short-circuit on a CONFIDENT zero-ahead count (a git failure yields NaN and falls through) and return the identical outcome:"empty" shape before the throw-prone churn. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds an early short-circuit in ChangesEmpty Branch Short-Circuit
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant landOneRepo
participant Git
participant AuditLog
participant MergeAgent
Caller->>landOneRepo: runAiMerge(task)
landOneRepo->>Git: rev-list --count integration..branch
Git-->>landOneRepo: commit count
alt count is 0
landOneRepo->>AuditLog: audit merge:ai-empty
landOneRepo-->>Caller: outcome: empty (noOp)
else count > 0
landOneRepo->>MergeAgent: create worktree, sync, merge
MergeAgent-->>Caller: outcome: merged
end
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR adds an early no-op path for AI merges when a task branch has no commits ahead. The main changes are:
Confidence Score: 4/5This is close, but the early no-op check should be fixed before merging.
packages/engine/src/merger-ai.ts Important Files Changed
Reviews (2): Last reviewed commit: "Merge branch 'main' into fix/empty-branc..." | Re-trigger Greptile |
| // non-workspace land hard-fails), so the merge gets transient-retried to | ||
| // exhaustion and the card is parked failed. Only short-circuit on a CONFIDENT | ||
| // 0: a git failure yields "" → parseInt → NaN (≠ 0) and falls through. | ||
| const aheadRaw = await git(["rev-list", "--count", `${integrationBranch}..${branch}`], repoRootDir).catch(() => ""); |
There was a problem hiding this comment.
This early return uses bare branch names for the rev-list range, while the surrounding code verifies refs/heads/${integrationBranch} explicitly. If a tag or other ref shares the integration branch name, Git can resolve the range against that ref and report 0, causing landOneRepo to return empty before the normal merge path even though the task branch still has work relative to the branch head.
| const aheadRaw = await git(["rev-list", "--count", `${integrationBranch}..${branch}`], repoRootDir).catch(() => ""); | |
| const aheadRaw = await git(["rev-list", "--count", `refs/heads/${integrationBranch}..refs/heads/${branch}`], repoRootDir).catch(() => ""); |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/engine/src/__tests__/merger-ai.test.ts (1)
437-461: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider covering the git-failure fallthrough case too.
This test covers the confident-zero short-circuit path well, but the accompanying source comment explicitly calls out that a
git rev-listfailure should fall through to the normal flow (NaN ≠ 0) rather than short-circuiting. A follow-up test asserting that behavior (e.g. by making the branch ref invalid) would guard against future regressions of that fallback safety net.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/engine/src/__tests__/merger-ai.test.ts` around lines 437 - 461, Add a follow-up test in merger-ai.test.ts for runAiMerge that covers the git rev-list failure fallback: make the branch ref invalid or otherwise force the rev-list check to fail, then assert the code does not short-circuit as a zero-ahead branch and instead continues into the normal merge flow (including mergeAgent being invoked). Use the existing zero-commits-ahead test as the pattern and reference runAiMerge, mergeAgent, and the zero-ahead short-circuit behavior to keep the regression coverage aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/engine/src/__tests__/merger-ai.test.ts`:
- Around line 437-461: Add a follow-up test in merger-ai.test.ts for runAiMerge
that covers the git rev-list failure fallback: make the branch ref invalid or
otherwise force the rev-list check to fail, then assert the code does not
short-circuit as a zero-ahead branch and instead continues into the normal merge
flow (including mergeAgent being invoked). Use the existing zero-commits-ahead
test as the pattern and reference runAiMerge, mergeAgent, and the zero-ahead
short-circuit behavior to keep the regression coverage aligned.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 02024b5a-1c93-4465-a7e9-8277d44f1dbc
📒 Files selected for processing (2)
packages/engine/src/__tests__/merger-ai.test.tspackages/engine/src/merger-ai.ts
| // non-workspace land hard-fails), so the merge gets transient-retried to | ||
| // exhaustion and the card is parked failed. Only short-circuit on a CONFIDENT | ||
| // 0: a git failure yields "" → parseInt → NaN (≠ 0) and falls through. | ||
| const aheadRaw = await git(["rev-list", "--count", `${integrationBranch}..${branch}`], repoRootDir).catch(() => ""); |
There was a problem hiding this comment.
The empty-branch check still lets Git resolve the integration side as a bare ref. When a repo has a tag or other ref named like the integration branch, rev-list can count from that ref instead of refs/heads/<integrationBranch> and return 0. This branch then returns outcome: "empty", skips the merge path, and finalizes the task as a no-op even though the task branch still has commits relative to the real branch head.
| const aheadRaw = await git(["rev-list", "--count", `${integrationBranch}..${branch}`], repoRootDir).catch(() => ""); | |
| const aheadRaw = await git(["rev-list", "--count", `refs/heads/${integrationBranch}..refs/heads/${branch}`], repoRootDir).catch(() => ""); |
Problem
When a coding agent produces zero commits relative to base, the AI-merge path wedges the card terminally:
runAiMerge→landOneRepobuilds a clean-room worktree and runs a dependency install.if (!ctx.nonFatalDependencySync) throw depsErr;).MAX_AUTO_MERGE_TRANSIENT_RETRIES→Auto-merge transient retries exhausted (3/3).failed(→ archived), even though the correct outcome for an empty branch is a no-op finalize.The truly-empty branch would reach
outcome: "empty"anyway viamergeAndReviewproducing nosquashSha— but only after the throw-prone churn that fails first.The canonical
aiMergeTask/classifyOwnedLandedEvidencepath already has an early empty-own-diff fast-path; therunAiMerge→landOneRepopath did not.Fix
Short-circuit inside
landOneRepo, right after thetipShacomputation and before the clean-room build, when the branch is a confident zero commits ahead of the integration tip:{ outcome: "empty", tipSha, integrationBranch }shape and the samemerge:ai-emptyaudit event the downstream already handles, sorunAiMerge's empty-outcome handling (block-to-todo / no-op finalize) is unchanged.0: a git failure yields""→parseInt→NaN(≠ 0) and falls through to the normal path — no behavior change on error.landOneRepo(notrunAiMerge) because it is shared by both the single-repo and workspace per-repo callers.Test
New test in
merger-ai.test.tsasserts the merge agent is never invoked for a 0-ahead branch, the result is a no-op,mainis unmoved, and the card moves todonewithpreserveProgress. Without the fix the branch reaches the clean room andmergeAndReviewinvokes the merge agent, so the assertion fails — it genuinely guards the short-circuit.Full engine merge-suite regression run is green (merger-ai, workspace-merger and lease variants, cleanup, dependency-sync, group-merge, classify-owned-landed-evidence).
🤖 Generated with Claude Code
Summary by CodeRabbit