fix(merger,dashboard): handle empty-squash commitSha to keep card and Changes tab in sync

When a branch contained commits already on main (duplicate cherry-picks),
the merger's local squash collapsed to an empty commit. The merger then
recorded that empty commit's SHA on mergeDetails.commitSha. The actual
content landed later on main as a different SHA via PR merge, but the
task kept pointing at the orphaned empty commit.

Symptom: TaskCard showed "N files changed" (falling back to
task.modifiedFiles), but the Changes tab in the modal showed nothing
because the API hit `git diff sha^..sha` on the empty commit and
returned no files.

Two fixes:

1. merger.ts: detect empty squash commits and skip storing commitSha,
   logging clearly. recoverInterruptedMergingTasks → findLandedTaskCommit
   already exists to backfill the right SHA when the real commit lands;
   a missing commitSha is a known fallback path the UI already handles.

2. TaskChangesTab.tsx: when the API returns no files for a done task,
   fall back to task.modifiedFiles (paths only, no patches) with a clear
   note. Mirrors the existing 3-tier fallback in TaskCard.tsx:1090-1124
   so card and modal always agree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-24 13:31:34 -07:00
parent c3fe4fd5b6
commit 85ba1071bd
3 changed files with 81 additions and 4 deletions

View File

@@ -2292,8 +2292,25 @@ export async function aiMergeTask(
deletions = deletionsMatch ? Number.parseInt(deletionsMatch[1], 10) : 0;
} catch { /* non-fatal */ }
// Guard: if the squash collapsed to an empty commit, recording its SHA
// misleads every consumer (TaskChangesTab shows "no changes" even though
// modifiedFiles is non-empty). Real cause: the branch contained commits
// already on main (duplicate cherry-picks), and conflict resolution
// dropped them. The actual landing typically happens later via PR merge
// on a different SHA — let recoverInterruptedMergingTasks /
// findLandedTaskCommit populate the right SHA when that lands. Until
// then, store mergeDetails without commitSha so the UI falls back to
// task.modifiedFiles instead of a broken diff.
const isEmptyCommit = filesChanged === 0;
const recordedSha = isEmptyCommit ? undefined : commitSha;
if (isEmptyCommit) {
mergerLog.warn(
`${taskId}: local squash produced an empty commit (${commitSha?.slice(0, 8)}) — branch likely contained dupes of main. Skipping commitSha; recovery will backfill when real commit lands.`,
);
}
const mergeDetails: MergeDetails = {
commitSha,
commitSha: recordedSha,
filesChanged,
insertions,
deletions,
@@ -2307,7 +2324,7 @@ export async function aiMergeTask(
};
await store.updateTask(taskId, { mergeDetails });
mergerLog.log(`${taskId}: merge details stored (commitSha: ${commitSha?.slice(0, 8)})`);
mergerLog.log(`${taskId}: merge details stored (commitSha: ${recordedSha?.slice(0, 8) ?? "<deferred>"})`);
} catch (err: any) {
mergerLog.warn(`${taskId}: failed to collect/store merge details: ${err.message}`);
}