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
- Resubmit immediately with {result: {data: <output>}} — the error message itself prescribes this shape and shows remaining retries
- If the task genuinely failed, resubmit with {result: {error: "description"}}
- If the output is already in the last assistant turn, use the useLastTurn yield variant instead of an empty data payload
- 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
- Treat an empty result object as a bug — always construct data or error explicitly
- Track the retry budget in the error message; fix the shape on the first attempt
- Use the useLastTurn variant when output lives in the final assistant message
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
- result cannot contain both data and error
- data is required when yield indicates success
- Section ${formatYieldLabels(yieldType as string[])} uses unk
- This task requires structured output matching the declared s
- ${scope} does not match schema: ${formatAllValidationIssues(
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/28d6c67633ee2108.
Report an issue: GitHub.