can1357/oh-my-pi · error

result must contain either \`data\` or \`error\`. Use \`{res

Error message

result must contain either \`data\` or \`error\`. Use \`{result: {data: <your output>}}\` for success or \`{result: {error: "message"}}\` for failure. Empty untyped result retries remaining before abort: ${remaining}.

What it means

The yield tool requires every result to carry either a `data` payload or an `error` message. When a result has neither (and no incremental `type` sections to fall back on), the tool counts the empty submission and throws, telling the caller how many retries remain before the subagent aborts. After MAX_EMPTY_RESULT_RETRIES empty results the tool aborts the task instead of retrying again.

Source

Thrown at packages/coding-agent/src/tools/yield.ts:403

			this.#emptyResultFailures++;
			if (this.#emptyResultFailures > MAX_EMPTY_RESULT_RETRIES) {
				const attemptCount = this.#emptyResultFailures;
				this.#emptyResultFailures = 0;
				const error =
					`yield result stayed empty after ${attemptCount} consecutive attempt(s); aborting child instead of retrying forever. ` +
					'Submit success as `{ "result": { "data": <your output> } }` or failure as `{ "result": { "error": "message" } }`.';
				return {
					content: [{ type: "text", text: `Task aborted: ${error}` }],
					details: {
						data: undefined,
						status: "aborted",
						error,
						type: yieldType,
					},
				};
			}
			const remaining = MAX_EMPTY_RESULT_RETRIES - this.#emptyResultFailures;
			throw new Error(
				`result must contain either \`data\` or \`error\`. Use \`{result: {data: <your output>}}\` for success or \`{result: {error: "message"}}\` for failure. Empty untyped result retries remaining before abort: ${remaining}.`,
			);
		}

		const status = errorMessage !== undefined ? "aborted" : "success";
		let schemaValidationOverridden = false;
		// Unknown incremental labels are a hard contract mismatch with the closed caller
		// schema. Reject before the last-turn short-circuit too: `type: ["findings"], result: {}`
		// would otherwise be accepted as a typed last-turn incremental yield, then a sibling
		// section's MAX_SCHEMA_RETRIES override flips schemaOverridden in finalization and the
		// stale section rides along untouched.
		if (status === "success" && isIncremental) {
			const unknownLabels = this.#unknownIncrementalLabels(yieldType as string[]);
			if (unknownLabels.length > 0) {
				const validLabels =
					this.#knownSectionLabels.length > 0 ? formatYieldLabels(this.#knownSectionLabels) : "none";
				throw new Error(
					`Section ${formatYieldLabels(yieldType as string[])} uses unknown incremental yield label(s): ${formatYieldLabels(unknownLabels)}. Resubmit with one of the schema's labels: ${validLabels}.`,

View on GitHub (pinned to 9690622007)

Solutions

  1. Resubmit immediately with {result: {data: <output>}} — the error message itself prescribes this shape and shows remaining retries
  2. If the task genuinely failed, resubmit with {result: {error: "description"}}
  3. If the output is already in the last assistant turn, use the useLastTurn yield variant instead of an empty data payload
  4. Fix the calling harness so it never submits an empty result object

Example fix

// before
yield({ result: {} });
// after
yield({ result: { data: { summary: "done", files: ["a.ts"] } } });
Defensive patterns

Strategy: validation

Validate before calling

function yieldPayloadIsValid(r) { return r.data !== undefined || r.error !== undefined || r.type !== undefined; }

Type guard

function hasContent(r) {
  return r.data !== undefined || r.error !== undefined || (r.useLastTurn === true);
}

Try / catch

try {
  yield({ result });
} catch (err) {
  if (err.message.includes('must contain either')) {
    yield({ result: { data: buildFallbackData() } });
  } else throw err;
}

Prevention

When it happens

Trigger: Calling the yield tool with {result: {}} or an all-undefined payload while no incremental section type is supplied; repeated empty yields (up to MAX_EMPTY_RESULT_RETRIES) then abort.

Common situations: A model emits a bare {result:{}} because it thinks the parent reads its message text; a templated caller leaves both data and error unset; an integration passes args through JSON round-trips that drop null/undefined payloads.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/28d6c67633ee2108. Report an issue: GitHub.