Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions .fork-features/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -256,4 +262,4 @@
}
}
}
}
}
2 changes: 1 addition & 1 deletion packages/opencode/src/session/async-tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 12 additions & 9 deletions packages/opencode/src/tool/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<never>((_, 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<never>((_, 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) {
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/tool/task.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down