diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts index fece68800b06..cdc6a30c9f4c 100644 --- a/packages/opencode/src/tool/task.ts +++ b/packages/opencode/src/tool/task.ts @@ -208,7 +208,9 @@ export const TaskTool = Tool.define( }, parts, }) - return result.parts.findLast((item) => item.type === "text")?.text ?? "" + const text = result.parts.findLast((item) => item.type === "text")?.text + if (!text?.trim()) return yield* Effect.fail(new Error("Task completed without text output")) + return text }) const resumeWhenIdle: (input: { userID: MessageID; state: "completed" | "error" }) => Effect.Effect = diff --git a/packages/opencode/test/tool/task.test.ts b/packages/opencode/test/tool/task.test.ts index 2b7d001572a0..2c3bc3d9547e 100644 --- a/packages/opencode/test/tool/task.test.ts +++ b/packages/opencode/test/tool/task.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect } from "bun:test" -import { Effect, Exit, Fiber, Layer } from "effect" +import { Cause, Effect, Exit, Fiber, Layer } from "effect" import { Agent } from "../../src/agent/agent" import { BackgroundJob } from "@/background/job" import { Bus } from "@/bus" @@ -376,6 +376,39 @@ describe("tool.task", () => { }), ) + it.instance("execute fails when the subagent returns empty text output", () => + Effect.gen(function* () { + const { chat, assistant } = yield* seed() + const tool = yield* TaskTool + const def = yield* tool.init() + + const exit = yield* def + .execute( + { + description: "inspect bug", + prompt: "look into the cache key path", + subagent_type: "general", + }, + { + sessionID: chat.id, + messageID: assistant.id, + agent: "build", + abort: new AbortController().signal, + extra: { promptOps: stubOps({ text: "" }) }, + messages: [], + metadata: () => Effect.void, + ask: () => Effect.void, + }, + ) + .pipe(Effect.exit) + + expect(Exit.isFailure(exit)).toBe(true) + if (Exit.isFailure(exit)) { + expect(Cause.squash(exit.cause)).toMatchObject({ message: "Task completed without text output" }) + } + }), + ) + it.instance( "execute shapes child permissions for task, todowrite, and primary tools", () =>