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 <noreply@anthropic.com> * 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 <noreply@anthropic.com> * 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 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -417,7 +417,12 @@ export class AgentQueueManager {
|
||||
|
||||
// Track completed types for progress calculation
|
||||
const completedTypes = new Set<string>();
|
||||
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) => {
|
||||
|
||||
@@ -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<string, ReturnType<typeof setTimeout>>();
|
||||
|
||||
@@ -266,7 +268,7 @@ export const useIdeationStore = create<IdeationState>((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: [] }),
|
||||
|
||||
Reference in New Issue
Block a user