fix(dashboard): prevent foreign-branch diffs after worktree pool reuse

When the worktree-recycle pool reassigned a path to a new task, the old
task's diff endpoints kept reading the new task's branch state — surfacing
unrelated commits as the original task's "files changed" list.

- Clear task.worktree/branch in the merger after the worktree is released
  to the pool or removed, so the path no longer points anywhere.
- Validate the worktree's current branch matches task.branch in the three
  worktree-backed diff endpoints; on mismatch return empty rather than
  diffing against a foreign branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-27 11:04:42 -07:00
parent f85d369bfc
commit 9650019877
4 changed files with 80 additions and 1 deletions

View File

@@ -358,6 +358,29 @@ describe("aiMergeTask — conditional worktree cleanup", () => {
expect(result.worktreeRemoved).toBe(true);
});
it("clears task.worktree/branch after the worktree is removed", async () => {
const worktreePath = "/tmp/root/.worktrees/KB-050";
const store = createMockStore(
{ id: "FN-050", worktree: worktreePath },
[
{ id: "FN-050", worktree: worktreePath, column: "in-review" } as Task,
],
);
await aiMergeTask(store, "/tmp/root", "FN-050");
// The dashboard's diff endpoint reads task.worktree to decide whether to
// run a live git diff; leaving it set after removal would point at a
// foreign branch (when the path is recycled) and surface other tasks'
// commits as if they belonged to FN-050.
const updateCalls = (store.updateTask as any).mock.calls;
const cleared = updateCalls.find(
([id, patch]: [string, any]) =>
id === "FN-050" && patch && patch.worktree === null && patch.branch === null,
);
expect(cleared).toBeDefined();
});
it("always deletes the branch regardless of worktree sharing", async () => {
const worktreePath = "/tmp/root/.worktrees/KB-050";
const store = createMockStore(
@@ -4049,7 +4072,7 @@ describe("resolveTaskDiffBaseRef", () => {
expect(diffBase).toBe("parent-123");
});
it("returns undefined when no merge base or fallback refs are available", async () => {
it("returns undefined when no merge base, no valid baseCommitSha, and no parent commit are available", async () => {
mockedExecSync.mockImplementation((cmd: any) => {
const cmdStr = String(cmd);
if (cmdStr === 'git merge-base "HEAD" "main"') {