## Problem
When a coding agent produces **zero commits** relative to base, the
AI-merge path wedges the card terminally:
1. `runAiMerge` → `landOneRepo` builds a clean-room worktree and runs a
dependency install.
2. On a non-workspace land the dep-install step throws hard (`if
(!ctx.nonFatalDependencySync) throw depsErr;`).
3. The throw is transient-classified and retried up to
`MAX_AUTO_MERGE_TRANSIENT_RETRIES` → `Auto-merge transient retries
exhausted (3/3)`.
4. The card is parked `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 via
`mergeAndReview` producing no `squashSha` — but only **after** the
throw-prone churn that fails first.
The canonical `aiMergeTask`/`classifyOwnedLandedEvidence` path already
has an early empty-own-diff fast-path; the `runAiMerge` → `landOneRepo`
path did not.
## Fix
Short-circuit inside `landOneRepo`, right after the `tipSha` computation
and **before** the clean-room build, when the branch is a confident zero
commits ahead of the integration tip:
```ts
const aheadRaw = await git(["rev-list", "--count", `${integrationBranch}..${branch}`], repoRootDir).catch(() => "");
if (Number.parseInt(aheadRaw.trim(), 10) === 0) {
await audit.git({ type: "merge:ai-empty", target: integrationBranch, metadata: { taskId, tipSha } });
return { outcome: "empty", tipSha, integrationBranch };
}
```
- Returns the **identical** `{ outcome: "empty", tipSha,
integrationBranch }` shape and the same `merge:ai-empty` audit event the
downstream already handles, so `runAiMerge`'s empty-outcome handling
(block-to-todo / no-op finalize) is unchanged.
- **Only** short-circuits on a confident `0`: a git failure yields `""`
→ `parseInt` → `NaN` (≠ 0) and falls through to the normal path — no
behavior change on error.
- Placed in `landOneRepo` (not `runAiMerge`) because it is shared by
both the single-repo and workspace per-repo callers.
## Test
New test in `merger-ai.test.ts` asserts the merge agent is **never
invoked** for a 0-ahead branch, the result is a no-op, `main` is
unmoved, and the card moves to `done` with `preserveProgress`. Without
the fix the branch reaches the clean room and `mergeAndReview` invokes
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](https://claude.com/claude-code)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Improved merge handling for branches with no new commits ahead of the
target branch.
* Empty or already-synced branches now finish as a no-op instead of
triggering merge work.
* Tasks in this scenario still move to done, while the main branch
remains unchanged.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->