Audit of merge commit sites surfaced four paths that could produce an
empty commit body when the branch had no unique commits relative to its
base (so `git log <base>..<branch>` returns empty), or when the `git log`
collection itself failed. Subject was always non-empty so git accepted
the commit, but the body was missing — breaking downstream consumers
(release notes, dashboard summaries, mergeDetails) that read it.
The AI merge agent is the primary author of merge commit messages; these
fallback paths only run when the agent didn't commit and the merger has
to commit on its behalf. Previously they used `-m "${commitLog}"` which
silently produced `-m ""` on empty input.
Now uses a 4-tier resolveSafeCommitBody cascade — most informative
first, with a deterministic floor so the function never returns empty
and never throws:
1. The branch's commit log if non-empty.
2. AI-generated body via aiGenerateCommitBody — a fresh readonly
session that summarizes the diff stat into 2–6 bullet points.
Bounded by a 30s timeout (forwards the caller's abort signal too)
so engine pause / shutdown tears it down promptly. Any failure
falls through.
3. The diff stat itself, formatted as a "Files changed" listing.
4. A synthetic `- merge <branch>` placeholder.
Wired into all three merger fallback commit sites:
- Auto-resolved-conflicts commit (Attempt 2 success path)
- -X ours / -X theirs side-strategy commit (Attempt 3)
- Agent-didn't-commit fallback commit (post-AI verification)
Also defensive: removed `--allow-empty-message` from the executor's
squash-import commit. The message is hardcoded non-empty (subject +
body), but the flag was a footgun — git would silently accept an empty
message if the construction ever broke. Switched to two `-m` args
(subject and body separately) so empty would now correctly fail at
git's level rather than silently land a blank-message commit.
Tests + checks: engine 2887/2887 pass, typecheck clean, workspace lint
clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>