#18509 · @Haohao-end · opened Mar 21, 2026 at 9:36 AM UTC · last updated Mar 21, 2026 at 9:51 AM UTC

fix(task): propagate subagent provider errors to orchestrator

appfix
66
+18412 files

Score breakdown

Impact

9.0

Clarity

9.0

Urgency

8.0

Ease Of Review

8.0

Guidelines

9.0

Readiness

9.0

Size

5.0

Trust

5.0

Traction

2.0

Summary

Subagent provider errors were not propagating to the orchestrator, causing it to spin indefinitely. This PR fixes the issue by explicitly checking for and throwing errors from child sessions. New focused regression tests are included.

Open in GitHub

Description

Problem

Provider/model failures inside subagent sessions were not reaching the primary orchestrator, so the parent could continue spinning instead of surfacing the real error.

Root cause

TaskTool.execute() treated the child session result as a successful assistant text response and ignored childAssistant.info.error, which downgraded provider failures into empty task results.

Fix

After SessionPrompt.prompt(...) returns, TaskTool.execute() now checks for info.error and throws a normal Error with the child error message. Successful child responses still follow the existing task_id + <task_result> path unchanged.

Validation

  • Added focused regression tests for successful subagent output propagation
  • Added focused regression tests for provider-error propagation from subagent to parent
  • Verified with:
    • bun test test/tool/task.test.ts

Linked Issues

None.

Comments

PR comments

Haohao-end

CI is currently failing on unrelated existing typecheck errors in packages/app:

  • src/components/dialog-connect-provider.tsx:386
  • src/components/dialog-custom-provider.tsx:134

My change only touches:

  • packages/opencode/src/tool/task.ts
  • packages/opencode/test/tool/task.test.ts

Local validation for this fix:

  • bun test test/tool/task.test.ts

Changed Files

packages/opencode/src/tool/task.ts

+70
@@ -144,6 +144,13 @@ export const TaskTool = Tool.define("task", async (ctx) => {
parts: promptParts,
})
if (result.info.role === "assistant" && result.info.error) {
const err = result.info.error
throw new Error(
typeof err.data?.message === "string" && err.data.message ? err.data.message : err.name || "Task failed",
)
}
const text = result.parts.findLast((x) => x.type === "text")?.text ?? ""
const output = [

packages/opencode/test/tool/task.test.ts

+1771
@@ -1,13 +1,103 @@
import { afterEach, describe, expect, test } from "bun:test"
import { afterEach, describe, expect, spyOn, test } from "bun:test"
import { Agent } from "../../src/agent/agent"
import { Instance } from "../../src/project/instance"
import { ModelID, ProviderID } from "../../src/provider/schema"
import { Session } from "../../src/session"
import { MessageV2 } from "../../src/session/message-v2"
import { SessionPrompt } from "../../src/session/prompt"
import { MessageID, PartID, SessionID } from "../../src/session/schema"
import { TaskTool } from "../../src/tool/task"
import { tmpdir } from "../fixture/fixture"
afterEach(async () => {
await Instance.disposeAll()
})
const providerID = ProviderID.make("test")
const modelID = ModelID.make("test-model")
async function seed() {
const session = await Session.create({})
const user = await Session.updateMessage({
id: MessageID.ascending(),
sessionID: session.id,
role: "user",
time: { created: Date.now() },
agent: "build",
model: { providerID, modelID },
})
const msg = await Session.updateMessage({
id: MessageID.ascending(),
sessionID: session.id,