can1357/oh-my-pi · error
Received thinking_end for non-thinking content
Error message
Received thinking_end for non-thinking content
What it means
thinking_end attaches the content signature to the thinking block at contentIndex; the slot must already be a thinking block. A missing or mismatched slot means the event stream is inconsistent with the partially built AssistantMessage, so the library throws rather than writing a signature onto the wrong block.
Source
Thrown at packages/agent/src/proxy.ts:319
delta: proxyEvent.delta,
partial,
};
}
throw new Error("Received thinking_delta for non-thinking content");
}
case "thinking_end": {
const content = partial.content[proxyEvent.contentIndex];
if (content?.type === "thinking") {
content.thinkingSignature = proxyEvent.contentSignature;
return {
type: "thinking_end",
contentIndex: proxyEvent.contentIndex,
content: content.thinking,
partial,
};
}
throw new Error("Received thinking_end for non-thinking content");
}
case "image_end":
partial.content[proxyEvent.contentIndex] = proxyEvent.content;
return {
type: "image_end",
contentIndex: proxyEvent.contentIndex,
content: proxyEvent.content,
partial,
};
case "toolcall_start":
partial.content[proxyEvent.contentIndex] = {
type: "toolCall",
id: proxyEvent.id,
name: proxyEvent.toolName,
arguments: {},
[kStreamingPartialJson]: "",View on GitHub (pinned to 9690622007)
Solutions
- Confirm the adapter closes reasoning on the same contentIndex it opened
- Inspect partial.content at failure time to find the actual block type occupying the index
- Avoid sharing one contentIndex between thinking and text blocks
- Align package versions between proxy producer and agent consumer
Defensive patterns
Strategy: type-guard
Validate before calling
if (partial.content[ev.contentIndex]?.type !== "thinking") {
throw new Error(`thinking_end at ${ev.contentIndex} without a thinking block`);
} Type guard
function canCloseThinking(partial: AssistantMessage, i: number): boolean {
return partial.content[i]?.type === "thinking";
} Try / catch
try {
processProxyEvent(model, ev, partial, partialJsonByIndex);
} catch (err) {
if (err instanceof Error && err.message.includes("thinking_end for non-thinking")) {
logger.warn("Ignoring orphan thinking_end", { contentIndex: ev.contentIndex });
return;
}
throw err;
} Prevention
- Close thinking on the same contentIndex that opened it
- Emit at most one thinking_end per thinking_start
- Keep stream state per-request; reset fully on retry
- Integration-test providers that emit signatures with reasoning blocks
When it happens
Trigger: thinking_end for an index that never received thinking_start; the block at the index was replaced by text/toolCall/image; duplicate or out-of-order thinking_end events.
Common situations: Custom providers whose reasoning stream ends on a shifted index; event replay where the start was pruned; providers that reuse indexes across block types in one turn.
Related errors
- Received thinking_delta for non-thinking content
- Received text_delta for non-text content
- Received text_end for non-text content
- Received toolcall_delta for non-toolCall content
- Devin Connect frame length ${len} exceeds ${MAX_CONNECT_FRAM
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/dabb1352801040c1.
Report an issue: GitHub.