diff --git a/.fork-features/manifest.json b/.fork-features/manifest.json index b7c66fd0ae4b..267b895fd0f2 100644 --- a/.fork-features/manifest.json +++ b/.fork-features/manifest.json @@ -165,12 +165,18 @@ }, "task-timeout": { "status": "active", - "description": "Increased default subagent task timeout from 5 minutes to 10 minutes. Upstream default is 5 minutes which is too short for complex tasks like sync operations and multi-file implementations.", - "issue": "https://github.com/randomm/opencode/issues/176", + "description": "Increased default subagent task timeout from upstream default of 5 minutes to 30 minutes. Both independent timeout constants updated: DEFAULT_TASK_TIMEOUT in async-tasks.ts (background tasks) and taskTimeoutMs in tool/task.ts (sync task execution). 30 minutes needed for complex agentic tasks like multi-file implementations and upstream syncs.", + "issue": "https://github.com/randomm/opencode/issues/198", "newFiles": [], "deletedFiles": [], - "modifiedFiles": ["packages/opencode/src/session/async-tasks.ts"], - "criticalCode": ["DEFAULT_TASK_TIMEOUT = 10 * 60 * 1000"], + "modifiedFiles": [ + "packages/opencode/src/session/async-tasks.ts", + "packages/opencode/src/tool/task.ts" + ], + "criticalCode": [ + "DEFAULT_TASK_TIMEOUT = 30 * 60 * 1000", + "taskTimeoutMs = 30 * 60 * 1000" + ], "tests": [], "upstreamTracking": { "absorptionSignals": ["TASK_TIMEOUT.*config", "task.*timeout.*env", "task_timeout"] @@ -256,4 +262,4 @@ } } } -} +} \ No newline at end of file diff --git a/packages/opencode/src/session/async-tasks.ts b/packages/opencode/src/session/async-tasks.ts index ddcab7ed0852..2eb5c86a1334 100644 --- a/packages/opencode/src/session/async-tasks.ts +++ b/packages/opencode/src/session/async-tasks.ts @@ -49,7 +49,7 @@ export interface BackgroundTaskResult { const MAX_STORED_TASK_RESULTS = 1000 const MAX_DELIVERED_RESULTS = 10000 -const DEFAULT_TASK_TIMEOUT = 10 * 60 * 1000 +const DEFAULT_TASK_TIMEOUT = 30 * 60 * 1000 const MAX_AGENT_DESCRIPTION_LENGTH = 200 const MAX_TASK_RESULT_LENGTH = 5000 const SECONDS_TO_MS_MULTIPLIER = 1000 diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index d17e7b3a4047..9cd9909ec10e 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -378,15 +378,16 @@ export const TaskTool = Tool.define("task", async (initCtx) => { release_slot: result.releaseSlot, } - const taskTimeoutMs = 10 * 60 * 1000 - const syncAbortController = new AbortController() - const timeoutPromise = new Promise((_, reject) => { - setTimeout(() => { - SessionPrompt.cancel(session.id) - syncAbortController.abort() - reject(new Error("Task timeout after 10 minutes")) - }, taskTimeoutMs) - }) +const taskTimeoutMs = 30 * 60 * 1000 +const syncAbortController = new AbortController() +let timeoutTimer: Timer +const timeoutPromise = new Promise((_, reject) => { + timeoutTimer = setTimeout(() => { + SessionPrompt.cancel(session.id) + syncAbortController.abort() + reject(new Error("Task timeout after 30 minutes")) + }, taskTimeoutMs) +}) // Check for abort before sync execution to prevent race condition if (ctx.abort.aborted) { @@ -428,6 +429,7 @@ export const TaskTool = Tool.define("task", async (initCtx) => { }), timeoutPromise, ]) + clearTimeout(timeoutTimer!) const textPart = promptResult.parts.find((p) => p.type === "text" && !p.synthetic) const textResult = textPart && "text" in textPart ? textPart.text : undefined if (result.releaseSlot) { @@ -444,6 +446,7 @@ export const TaskTool = Tool.define("task", async (initCtx) => { metadata: { sessionId: session.id } as TaskResultMetadata, } } catch (e) { + clearTimeout(timeoutTimer!) if (!slotReleased && result.releaseSlot) { result.releaseSlot() slotReleased = true diff --git a/packages/opencode/src/tool/task.txt b/packages/opencode/src/tool/task.txt index 163773e4f8a6..2de18472d612 100644 --- a/packages/opencode/src/tool/task.txt +++ b/packages/opencode/src/tool/task.txt @@ -62,7 +62,7 @@ Response Output: { "task_id": "task_123", "status": "started", "message": "Task dispatched to @agent" } ``` -Note: Both modes respect the 5-task limit per session and 10-minute timeout. +Note: Both modes respect the 5-task limit per session and 30-minute timeout. Task lifecycle management: - If new information makes a running task obsolete, cancel it with cancel_task before launching a replacement. Do not leave stale tasks running.