From e6e8da17c8cf94546b89ee8012d917b308ede22f Mon Sep 17 00:00:00 2001 From: VDT-91 Date: Wed, 4 Feb 2026 12:17:36 +0100 Subject: [PATCH] fix: resolve ideation stuck at 3/6 types bug (#1660) * fix: resolve ideation stuck at 3/6 types bug Root causes identified and fixed: 1. Missing agent_type="ideation" in generator.py - Was defaulting to "coder" which loads MCP servers - MCP servers caused 60-second timeout delays per ideation type - Added agent_type="ideation" to both create_client() calls 2. No timeout protection on asyncio.gather() in runner.py - One stuck task could block forever - Added 5-minute timeout with proper error handling 3. Hardcoded totalTypes=7 in agent-queue.ts - There are exactly 6 ideation types, not 7 - Progress calculation was always wrong (3/7 vs 3/6) 4. Log buffer limited to 100 lines in ideation-store.ts - Error messages were being truncated - Increased to 500 lines for better debugging Co-Authored-By: Claude Opus 4.5 * fix: properly cancel asyncio tasks on ideation timeout Prevents resource leaks by explicitly creating tasks with asyncio.create_task() and cancelling them when the 5-minute timeout is reached. This ensures orphaned tasks don't continue consuming API calls or writing files after timeout. Co-Authored-By: Claude Opus 4.5 * fix: resolve CI failures and address PR review feedback - Fix timeout handling in ideation runner to preserve completed results instead of discarding all results on timeout (HIGH priority feedback) - Extract IDEATION_TIMEOUT_SECONDS constant to replace magic number - Derive totalTypes from --types argument instead of hardcoding 6 - Extract MAX_LOG_ENTRIES constant from magic number 500 - Fix Ruff formatting in parallel_orchestrator_reviewer.py and pydantic_models.py - Fix test_integration_phase4.py to register module in sys.modules before exec_module for Python 3.12+ dataclass compatibility Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 --- apps/backend/ideation/generator.py | 5 +++ apps/backend/ideation/runner.py | 42 ++++++++++++++++--- apps/frontend/src/main/agent/agent-queue.ts | 7 +++- .../src/renderer/stores/ideation-store.ts | 4 +- 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/apps/backend/ideation/generator.py b/apps/backend/ideation/generator.py index 3068a68e..b03a097e 100644 --- a/apps/backend/ideation/generator.py +++ b/apps/backend/ideation/generator.py @@ -91,11 +91,14 @@ class IdeationGenerator: prompt += f"\n{additional_context}\n" # Create client with thinking budget + # Use agent_type="ideation" to avoid loading unnecessary MCP servers + # which can cause 60-second timeout delays client = create_client( self.project_dir, self.output_dir, resolve_model_id(self.model), max_thinking_tokens=self.thinking_budget, + agent_type="ideation", ) try: @@ -184,11 +187,13 @@ Common fixes: Write the fixed JSON to the file now. """ + # Use agent_type="ideation" for recovery agent as well client = create_client( self.project_dir, self.output_dir, resolve_model_id(self.model), max_thinking_tokens=self.thinking_budget, + agent_type="ideation", ) try: diff --git a/apps/backend/ideation/runner.py b/apps/backend/ideation/runner.py index dfe1e81e..70fb2fbc 100644 --- a/apps/backend/ideation/runner.py +++ b/apps/backend/ideation/runner.py @@ -28,6 +28,7 @@ from .types import IdeationPhaseResult # Configuration MAX_RETRIES = 3 +IDEATION_TIMEOUT_SECONDS = 5 * 60 # 5 minutes max for all ideation types class IdeationOrchestrator: @@ -173,16 +174,45 @@ class IdeationOrchestrator: "progress", ) - # Create tasks for all enabled types - ideation_tasks = [ - self.output_streamer.stream_ideation_result( - ideation_type, self.phase_executor, MAX_RETRIES + # Create tasks explicitly so we can cancel them on timeout + ideation_task_objs = [ + asyncio.create_task( + self.output_streamer.stream_ideation_result( + ideation_type, self.phase_executor, MAX_RETRIES + ) ) for ideation_type in self.enabled_types ] - # Run all ideation types concurrently - ideation_results = await asyncio.gather(*ideation_tasks, return_exceptions=True) + # Run all ideation types concurrently with timeout protection + # 5 minute timeout prevents infinite hangs if one type stalls + try: + ideation_results = await asyncio.wait_for( + asyncio.gather(*ideation_task_objs, return_exceptions=True), + timeout=IDEATION_TIMEOUT_SECONDS, + ) + except asyncio.TimeoutError: + print_status( + "Ideation generation timed out after 5 minutes", + "error", + ) + # Cancel all pending tasks to prevent resource leaks + for task in ideation_task_objs: + if not task.done(): + task.cancel() + # Wait for cancellation to complete and preserve results from completed tasks + # Tasks that finished before timeout will return their results; + # cancelled tasks will return CancelledError + results_after_cancel = await asyncio.gather( + *ideation_task_objs, return_exceptions=True + ) + # Convert CancelledError to timeout exception, preserve completed results + ideation_results = [ + Exception("Ideation timed out") + if isinstance(res, asyncio.CancelledError) + else res + for res in results_after_cancel + ] # Process results for i, result in enumerate(ideation_results): diff --git a/apps/frontend/src/main/agent/agent-queue.ts b/apps/frontend/src/main/agent/agent-queue.ts index 0dcad18d..fc9233ca 100644 --- a/apps/frontend/src/main/agent/agent-queue.ts +++ b/apps/frontend/src/main/agent/agent-queue.ts @@ -417,7 +417,12 @@ export class AgentQueueManager { // Track completed types for progress calculation const completedTypes = new Set(); - const totalTypes = 7; // Default all types + // Derive totalTypes from --types argument instead of hardcoding + const typesArgIndex = args.findIndex((arg) => arg === '--types'); + const totalTypes = + typesArgIndex > -1 && args[typesArgIndex + 1] + ? args[typesArgIndex + 1].split(',').length + : 6; // Default to 6 if not specified // Handle stdout - explicitly decode as UTF-8 for cross-platform Unicode support childProcess.stdout?.on('data', (data: Buffer) => { diff --git a/apps/frontend/src/renderer/stores/ideation-store.ts b/apps/frontend/src/renderer/stores/ideation-store.ts index f29232e1..dadefb7f 100644 --- a/apps/frontend/src/renderer/stores/ideation-store.ts +++ b/apps/frontend/src/renderer/stores/ideation-store.ts @@ -11,6 +11,8 @@ import type { import { DEFAULT_IDEATION_CONFIG } from '../../shared/constants'; const GENERATION_TIMEOUT_MS = 5 * 60 * 1000; +/** Maximum number of log entries to retain in memory for debugging */ +const MAX_LOG_ENTRIES = 500; const generationTimeoutIds = new Map>(); @@ -266,7 +268,7 @@ export const useIdeationStore = create((set) => ({ addLog: (log) => set((state) => ({ - logs: [...state.logs, log].slice(-100) // Keep last 100 logs + logs: [...state.logs, log].slice(-MAX_LOG_ENTRIES) })), clearLogs: () => set({ logs: [] }),