fix: Prevent stale worktree data from overriding correct task status (#1710)

* fix: Prevent stale worktree data from overriding correct task status

Fixed task deduplication logic in ProjectStore.getTasks() to use
status-based priority instead of blindly preferring worktree version.

Root cause: When the same task ID exists in both main project and worktree,
the old code blindly preferred the worktree version. Stale worktree data with
"in_progress" status would override the correct "done" status from main project.

Solution: Implemented status priority system where more complete statuses
(done: 100, pr_created: 90, human_review: 80, etc.) win over less complete
statuses (in_progress: 50, backlog: 30, queue: 20, error: 10).

This fixes the bug where switching between projects would cause tasks to
incorrectly show as "In Progress" when they should be "Done".

* refactor: Extract TASK_STATUS_PRIORITY to shared constant

Address PR review feedback: Move statusPriority map from inline
definition in project-store.ts to shared constant in task.ts,
following existing pattern for task-related constants.

Changes:
- Add TASK_STATUS_PRIORITY to apps/frontend/src/shared/constants/task.ts
- Import and use TASK_STATUS_PRIORITY in project-store.ts
- Add clarifying comment for tie-break behavior (main wins on ties)

* fix: Correct TASK_STATUS_PRIORITY order for backlog/queue

The priority values were inverted, causing stale worktree tasks with
status "backlog" (priority 30) to override main project tasks with
status "queue" (priority 20). This was backwards since queue comes
AFTER backlog in the workflow.

Changed:
- backlog: 30 → 20 (lower priority, comes first)
- queue: 20 → 30 (higher priority, comes after backlog)

This ensures that more advanced workflow stages always have higher
priority, preventing stale worktree data from overriding correct task
status during deduplication.

* fix: Prefer main project tasks over worktree during deduplication

When deduplicating tasks that exist in both main project and worktree,
the main project version should ALWAYS be preferred over worktree,
regardless of status priority. This prevents stale worktree data from
overriding correct task status after user manually moves tasks.

Also fixes TASK_STATUS_PRIORITY order for early workflow stages:
- backlog: 30 → 20 (lower priority, comes first)
- queue: 20 → 30 (higher priority, comes after backlog)

The status priority is now only used as a tie-breaker when comparing
tasks from the same location (e.g., two worktree versions).

Fixes issues where:
1. Dragging task from "human_review" to "queue" would revert back
   after switching projects
2. Stale worktree with "backlog" would override main project's "queue"

---------

Co-authored-by: Andy <119136210+AndyMik90@users.noreply.github.com>
This commit is contained in:
Burak
2026-02-04 14:09:33 +01:00
committed by GitHub
parent a5e3cc9a2a
commit fe08c644c4
2 changed files with 46 additions and 3 deletions
+30 -3
View File
@@ -3,7 +3,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, Dirent
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import type { Project, ProjectSettings, Task, TaskStatus, TaskMetadata, ImplementationPlan, ReviewReason, PlanSubtask, KanbanPreferences, ExecutionPhase } from '../shared/types';
import { DEFAULT_PROJECT_SETTINGS, AUTO_BUILD_PATHS, getSpecsDir, JSON_ERROR_PREFIX, JSON_ERROR_TITLE_SUFFIX } from '../shared/constants';
import { DEFAULT_PROJECT_SETTINGS, AUTO_BUILD_PATHS, getSpecsDir, JSON_ERROR_PREFIX, JSON_ERROR_TITLE_SUFFIX, TASK_STATUS_PRIORITY } from '../shared/constants';
import { getAutoBuildPath, isInitialized } from './project-initializer';
import { getTaskWorktreeDir } from './worktree-paths';
import { isValidTaskId, findAllSpecPaths } from './utils/spec-path-helpers';
@@ -324,12 +324,39 @@ export class ProjectStore {
}
}
// 3. Deduplicate tasks by ID (prefer worktree version if exists in both)
// 3. Deduplicate tasks by ID
// CRITICAL FIX: Don't blindly prefer worktree - it may be stale!
// If main project task is "done", it should win over worktree's "in_progress".
// Worktrees can linger after completion, containing outdated task data.
const taskMap = new Map<string, Task>();
for (const task of allTasks) {
const existing = taskMap.get(task.id);
if (!existing || task.location === 'worktree') {
if (!existing) {
// First occurrence wins
taskMap.set(task.id, task);
} else {
// PREFER MAIN PROJECT over worktree - main has current user changes
// Only use status priority when both are from same location
const existingIsMain = existing.location === 'main';
const newIsMain = task.location === 'main';
if (existingIsMain && !newIsMain) {
// Main wins, keep existing
continue;
} else if (!existingIsMain && newIsMain) {
// New is main, replace existing worktree
taskMap.set(task.id, task);
} else {
// Same location - use status priority to determine which is more complete
const existingPriority = TASK_STATUS_PRIORITY[existing.status] || 0;
const newPriority = TASK_STATUS_PRIORITY[task.status] || 0;
if (newPriority > existingPriority) {
// New version has higher priority (more complete status)
taskMap.set(task.id, task);
}
// Otherwise keep existing version
}
}
}
@@ -3,6 +3,8 @@
* Includes status, categories, complexity, priority, and execution phases
*/
import type { TaskStatus } from '@shared/types/task';
// ============================================
// Task Status (Kanban columns)
// ============================================
@@ -47,6 +49,20 @@ export const TASK_STATUS_COLORS: Record<TaskStatusColumn | 'pr_created' | 'error
error: 'bg-destructive/10 text-destructive'
};
// Status priority for deduplication: higher = more complete
// Used in project-store.ts to resolve duplicate tasks (main vs worktree)
// IMPORTANT: Must follow workflow order: backlog < queue < in_progress < review < done
export const TASK_STATUS_PRIORITY: Record<TaskStatus, number> = {
'done': 100, // Highest priority - task is complete
'pr_created': 90,
'human_review': 80,
'ai_review': 70,
'in_progress': 50,
'queue': 30,
'backlog': 20,
'error': 10 // Lowest priority
} as const;
// ============================================
// Subtask Status
// ============================================