* chore: update .gitignore to include auto-generated files and security logs - Added entries for .security-key and logs/security/ to ignore auto-generated files and security logs. * fix(ACS-51): prevent task workflow from halting after planning stage Root cause: Frontend accepted incomplete plan data (empty phases array) during spec creation, which overwrote subtask state and left tasks stuck. Changes: - Add validatePlanData() to reject incomplete plans in task-store - Add reloadPlanForIncompleteTask() hook for resume functionality - Enhance logging in project-store for plan loading diagnostics - Add comprehensive unit tests for plan validation edge cases - Add integration tests for task lifecycle IPC events - Add E2E test specs for full task workflow The fix ensures incomplete plans are rejected while the backend's validation/auto-fix pipeline completes, preserving UI state until valid data arrives. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(ACS-55, ACS-71): ensure Kanban state transitions render correctly ACS-55: Task card was showing "planning" even after moving to "coding" phase - Phase transitions now bypass the 16ms batching window and apply immediately - Added debug logging when sequence number checks drop out-of-order updates - This ensures intermediate phases (planning→coding→qa) are never coalesced ACS-71: Task immediately moved to Human Review with zero subtasks - Exit handler now checks if subtasks exist before moving to human_review - Added validateStatusTransition() function to prevent invalid state changes - Blocks human_review when no subtasks exist (task still in planning) - Blocks phase regression from coding back to planning Changes: - agent-events-handlers.ts: Added validation function, fixed exit handler - useIpc.ts: Phase changes bypass batching, apply immediately - task-store.ts: Added logging for dropped out-of-order updates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: prevent status flip-flop between Human Review and AI Review When a task completed, `updateTaskFromPlan` would override the correct 'human_review' status with 'ai_review' when all subtasks were complete, causing tasks to flip between statuses on refresh. Root cause: The function only checked for "active" phases (planning, coding, qa_review, qa_fixing). When phase was 'complete' or 'idle', it would recalculate status from subtasks and set 'ai_review'. Fix: - Add 'complete' and 'failed' as terminal phases that skip recalculation - Respect explicit 'human_review' status from plan file - Never downgrade from 'human_review' to 'ai_review' This completes the Kanban state management fixes for ACS-51, ACS-55, ACS-71. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: add missing SubtaskStatus import to task-store The SubtaskStatus type was used but not imported, causing TypeScript compilation to fail in CI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use secure temp directories in tests to fix CodeQL alerts Replace hardcoded /tmp/ paths with mkdtempSync for secure temp directory creation. This prevents TOCTOU (time-of-check-time-of-use) attacks by using randomly generated directory names. Files fixed: - e2e/task-workflow.spec.ts - __tests__/integration/task-lifecycle.test.ts Resolves CodeQL "Insecure temporary file" high severity alerts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address PR review findings for Kanban state management - Fix reloadPlanForIncompleteTask to update Zustand store after reload - Extend flip-flop prevention to include pr_created and done statuses - Use wouldPhaseRegress() utility instead of hardcoded phase checks - Gate debug logging with debugLog utility for production - Fix unsafe type assertion for plan status - Remove redundant gitignore entry (logs/security/) - Add test coverage for terminal phase and status preservation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: address follow-up PR review suggestions (5 LOW severity) - Add ExecutionPhase type cast after type guard check - Use crypto.randomUUID() for stronger subtask ID generation - Add optional chaining for defensive coding in useTaskDetail - Clarify comment about phase bypass batching behavior - Fix misleading test comment about human_review preservation - Update test regex to accept both UUID and fallback ID formats 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: address final 3 LOW severity suggestions from CodeRabbit - Remove unused electronAPI variable in task-lifecycle test - Add comment explaining defensive fallback for description field - Rename test to clarify status recalculation skip behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
11 KiB
Root Cause Investigation: Task Workflow Halts After Planning Stage
Investigation Summary
After adding comprehensive logging to the task loading and plan update pipeline, I've analyzed the data flow from backend to frontend to identify why subtasks fail to display after spec completion.
Data Flow Analysis
Current Architecture
Backend (Python)
↓
Creates implementation_plan.json
↓
Emits IPC event: 'task:progress' with plan data
↓
Frontend (Electron Renderer)
↓
useIpc.ts: onTaskProgress handler (batched)
↓
task-store.ts: updateTaskFromPlan(taskId, plan)
↓
Creates subtasks from plan.phases.flatMap(phase => phase.subtasks)
↓
UI: TaskSubtasks.tsx renders subtasks
Critical Code Paths
1. Plan Update Handler (apps/frontend/src/renderer/hooks/useIpc.ts:131-135)
window.electronAPI.onTaskProgress(
(taskId: string, plan: ImplementationPlan) => {
queueUpdate(taskId, { plan });
}
);
2. Subtask Creation (apps/frontend/src/renderer/stores/task-store.ts:124-133)
const subtasks: Subtask[] = plan.phases.flatMap((phase) =>
phase.subtasks.map((subtask) => ({
id: subtask.id,
title: subtask.description,
description: subtask.description,
status: subtask.status,
files: [],
verification: subtask.verification as Subtask['verification']
}))
);
3. Initial Task Loading (apps/frontend/src/main/project-store.ts:461-470)
const subtasks = plan?.phases?.flatMap((phase) => {
const items = phase.subtasks || (phase as { chunks?: PlanSubtask[] }).chunks || [];
return items.map((subtask) => ({
id: subtask.id,
title: subtask.description,
description: subtask.description,
status: subtask.status,
files: []
}));
}) || [];
Root Cause Identification
Primary Root Cause: Early Plan Update Event with Empty Phases
What's Happening:
-
Backend creates
implementation_plan.jsonin stages:- First writes the file with minimal structure:
{ "feature": "...", "phases": [] } - Then adds phases and subtasks incrementally
- Emits IPC event each time the plan is updated
- First writes the file with minimal structure:
-
Frontend receives the FIRST plan update event:
- Plan has
featureand basic metadata - But
phasesarray is EMPTY:[] updateTaskFromPlanis called with this incomplete plan- Subtasks are created as empty array:
plan.phases.flatMap(...)→[]
- Plan has
-
Later plan updates with full subtask data are ignored:
- When backend writes the complete plan with subtasks
- Another IPC event is emitted
- But due to race conditions or event handling issues, this update doesn't reach the frontend
- Or it does reach but the task UI doesn't refresh
Evidence from Code:
Looking at updateTaskFromPlan (task-store.ts:106-190):
- Line 108-114: Logs show
phases: plan.phases?.length || 0 - Line 112: If plan has 0 phases,
totalSubtaskswill be 0 - Line 124-133:
plan.phases.flatMap(...)on empty array createssubtasks = [] - No validation to check if plan is complete before updating state
Why "!" Indicators Appear:
The "!" indicators likely come from the UI attempting to render subtasks when:
- Subtask count shows as 18 (from later plan update metadata)
- But
task.subtasksarray is actually empty[](from early plan update) - This mismatch causes the UI to show warning indicators
Secondary Contributing Factors
A. No Plan Validation Before State Update
Current code in updateTaskFromPlan immediately creates subtasks from whatever plan data it receives:
const subtasks: Subtask[] = plan.phases.flatMap((phase) =>
phase.subtasks.map((subtask) => ({ ... }))
);
Problem: No check if plan is "ready" or "complete" before updating state.
B. Missing Reload Trigger After Spec Completion
When spec creation completes and the full plan is written:
- The IPC event might not fire again
- Or the event fires but the batching mechanism drops it
- Frontend state remains stuck with empty subtasks
C. Race Condition in Batch Update Queue
In useIpc.ts:92-112, the batching mechanism queues updates:
function queueUpdate(taskId: string, update: BatchedUpdate): void {
const existing = batchQueue.get(taskId) || {};
batchQueue.set(taskId, { ...existing, ...update });
}
Problem: If two plan updates arrive within 16ms:
- First update has empty phases:
{ plan: { phases: [] } } - Second update has full phases:
{ plan: { phases: [...18 subtasks...] } } - Second update overwrites first in the queue
- But if order gets reversed, empty plan overwrites full plan
Log Evidence to Look For
To confirm this root cause, check console logs for:
1. Plan Loading Sequence
[updateTaskFromPlan] called with plan:
taskId: "xxx"
feature: "..."
phases: 0 ← SMOKING GUN: phases array is empty
totalSubtasks: 0 ← No subtasks
If you see phases: 0 followed later by no update with phases: 3 (or more), the early empty plan is stuck in state.
2. Multiple Plan Updates
[updateTaskFromPlan] called with plan:
phases: 0
totalSubtasks: 0
[updateTaskFromPlan] called with plan: ← This might never appear
phases: 3
totalSubtasks: 18
If second log never appears, the plan update event isn't firing after spec completion.
3. Project Store Loading
[ProjectStore] Loading implementation_plan.json for spec: xxx
[ProjectStore] Loaded plan for xxx:
phaseCount: 0 ← Empty plan loaded from disk
subtaskCount: 0
If plan file on disk has empty phases, the issue is in backend plan writing.
4. Plan File Utils
[plan-file-utils] Reading implementation_plan.json to update status
[plan-file-utils] Successfully persisted status ← Plan exists but might be incomplete
Check if plan file reads/writes are happening during spec creation.
Proposed Fix Approach
Fix 1: Add Plan Completeness Validation (Immediate Fix)
File: apps/frontend/src/renderer/stores/task-store.ts
Change: Only update subtasks if plan has valid phases and subtasks:
updateTaskFromPlan: (taskId, plan) =>
set((state) => {
console.log('[updateTaskFromPlan] called with plan:', { ... });
const index = findTaskIndex(state.tasks, taskId);
if (index === -1) {
console.log('[updateTaskFromPlan] Task not found:', taskId);
return state;
}
// VALIDATION: Don't update if plan is incomplete
if (!plan.phases || plan.phases.length === 0) {
console.warn('[updateTaskFromPlan] Plan has no phases, skipping update:', taskId);
return state; // Keep existing state, don't overwrite with empty data
}
const totalSubtasks = plan.phases.reduce((acc, p) => acc + (p.subtasks?.length || 0), 0);
if (totalSubtasks === 0) {
console.warn('[updateTaskFromPlan] Plan has no subtasks, skipping update:', taskId);
return state; // Keep existing state
}
// ... rest of existing code to create subtasks ...
})
Fix 2: Trigger Reload After Spec Completion (Comprehensive Fix)
File: apps/frontend/src/renderer/hooks/useIpc.ts
Change: Add explicit "spec completed" event handler that reloads the task:
// Add new IPC event listener
const cleanupSpecComplete = window.electronAPI.onSpecComplete(
async (taskId: string) => {
console.log('[IPC] Spec completed for task:', taskId);
// Force reload the task from disk to get the complete plan
const task = useTaskStore.getState().tasks.find(t => t.id === taskId);
if (task) {
// Reload plan from file
const result = await window.electronAPI.getTaskPlan(task.projectId, taskId);
if (result.success && result.data) {
updateTaskFromPlan(taskId, result.data);
}
}
}
);
Fix 3: Prevent Plan Overwrite in Batch Queue (Race Condition Fix)
File: apps/frontend/src/renderer/hooks/useIpc.ts
Change: Don't overwrite plan if incoming plan has fewer subtasks than existing:
function queueUpdate(taskId: string, update: BatchedUpdate): void {
const existing = batchQueue.get(taskId) || {};
// For plan updates, only accept if it has MORE data than existing
let mergedPlan = existing.plan;
if (update.plan) {
const existingSubtasks = existing.plan?.phases?.flatMap(p => p.subtasks || []).length || 0;
const newSubtasks = update.plan.phases?.flatMap(p => p.subtasks || []).length || 0;
if (newSubtasks >= existingSubtasks) {
mergedPlan = update.plan; // Accept new plan
} else {
console.warn('[IPC Batch] Rejecting plan update with fewer subtasks:',
{ taskId, existing: existingSubtasks, new: newSubtasks });
// Keep existing plan, don't overwrite with less complete data
}
}
// ... rest of existing code ...
}
Testing the Fix
Manual Verification Steps
- Create a new task and move it to "In Progress"
- Watch the console logs for:
[updateTaskFromPlan] called with plan: { phases: 0, totalSubtasks: 0 } - Wait for spec to complete (planning phase finishes)
- Check console logs for:
[updateTaskFromPlan] called with plan: { phases: 3, totalSubtasks: 18 } - Expand subtask list in task card
- Verify: Subtasks display with full details, no "!" indicators
Expected Outcome After Fix
- ✅ Empty/incomplete plan updates are ignored
- ✅ Only complete plans with phases and subtasks update the UI
- ✅ Subtasks display with id, description, and status
- ✅ No "!" warning indicators
- ✅ Subtask count shows "0/18 completed" (not "0/0")
- ✅ Plan pulsing animation stops when spec completes
- ✅ Resume functionality works without infinite loop
Next Steps
- ✅ This Investigation - Root cause identified (COMPLETE)
- 🔄 Subtask 2-1 - Implement Fix 1 (validation in updateTaskFromPlan)
- 🔄 Subtask 2-2 - Add data validation before subtask state updates
- 🔄 Subtask 2-3 - Fix pulsing animation condition
- 🔄 Subtask 2-4 - Fix resume logic to reload plan if subtasks missing
- 🔄 Phase 3 - Add comprehensive tests to prevent regressions
Conclusion
Root Cause: Frontend receives and accepts incomplete plan data (empty phases array) during the spec creation process, before subtasks are written. This overwrites any existing subtask data and leaves the UI in a stuck state with no subtasks to display.
Fix Priority: Implement Fix 1 (validation) immediately to prevent incomplete plans from updating state. This is a minimal, low-risk change that will resolve the core issue.
Long-term Solution: Add explicit event handling for spec completion (Fix 2) and improve batch queue logic (Fix 3) to make the system more robust against race conditions and out-of-order updates.