From d075ce5b5f67a2eff59d05f64ab5c9f41520f0d7 Mon Sep 17 00:00:00 2001 From: Janni Turunen Date: Wed, 18 Feb 2026 10:51:20 +0200 Subject: [PATCH 1/2] feat(async-tasks): increase default task timeout to 30 minutes (#198) --- .fork-features/manifest.json | 16 +++++++++++----- packages/opencode/src/session/async-tasks.ts | 2 +- packages/opencode/src/tool/task.ts | 18 +++++++++--------- 3 files changed, 21 insertions(+), 15 deletions(-) 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..c046247b53a8 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -378,15 +378,15 @@ 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() +const timeoutPromise = new Promise((_, reject) => { + 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) { From 32a611338a2d2fdd0d096a8371d83e668aca5099 Mon Sep 17 00:00:00 2001 From: Janni Turunen Date: Wed, 18 Feb 2026 10:55:54 +0200 Subject: [PATCH 2/2] fix(async-tasks): clear timeout timer on early completion, update doc (#198) --- packages/opencode/src/tool/task.ts | 5 ++++- packages/opencode/src/tool/task.txt | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index c046247b53a8..9cd9909ec10e 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -380,8 +380,9 @@ export const TaskTool = Tool.define("task", async (initCtx) => { const taskTimeoutMs = 30 * 60 * 1000 const syncAbortController = new AbortController() +let timeoutTimer: Timer const timeoutPromise = new Promise((_, reject) => { - setTimeout(() => { + timeoutTimer = setTimeout(() => { SessionPrompt.cancel(session.id) syncAbortController.abort() reject(new Error("Task timeout after 30 minutes")) @@ -428,6 +429,7 @@ const timeoutPromise = new Promise((_, reject) => { }), 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 @@ const timeoutPromise = new Promise((_, reject) => { 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.