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:
@@ -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
|
||||
// ============================================
|
||||
|
||||
Reference in New Issue
Block a user