Summary
On a Go plan account, the extension falls back to the legacy /alpha/generate transport (the Provider API answers 403 upgrade_required). On that transport, messagesToCC() interleaves image parts between consecutive tool results:
assistant: [tool-call A, tool-call B]
tool: [tool-result A]
user: [image A] <-- inserted here
tool: [tool-result B]
user: [image B]
The gateway closes the tool turn as soon as it sees the user message, so call B is still unanswered and the request aborts with:
Error: Tool result is missing for tool call call_01_...: server_error
Error: Retry failed after 3 attempts: Tool result is missing for tool call call_01_...: server_error
The failure is deterministic: all 3 retries send the same body and fail identically. The session JSONL stays valid (every toolCall has exactly one toolResult, no orphans, no duplicates) — only the serialized request is malformed.
Minimal reproduction
- Be on a Command Code Go plan so the transport router selects
/alpha/generate.
- Have two images on disk (
/tmp/a.png, /tmp/b.png).
- Ask the agent to read both in the same assistant turn, so the model issues two parallel
read calls.
read both of these screenshots /tmp/a.png /tmp/b.png
Result: Tool result is missing for tool call ...: server_error, stuck after 3 retries.
Reading one image per turn succeeds every time in the same session. Reading two in one turn fails every time. Three would fail the same way.
Local verification of the cause
I ran the exact message array through messagesToCC() from src/converters.ts (0.6.4) using the real session context that failed:
user: text
assistant: tool-call, tool-call
tool: tool-result
user: image <-- tool result B has not been emitted yet
tool: tool-result
user: image
Then I applied the diff from #84 locally and re-ran the same context:
assistant: tool-call, tool-call
tool: tool-result
tool: tool-result
user: image, image <-- batched, at the end
The malformed ordering is gone. Regression checks with the patched converter for 1 / 2 / 3 parallel image reads, text-only models (allowImages: false), tool errors, and mixed text+image results all produce the expected shapes.
Note: pi-ai already does this correctly
pi-ai/dist/api/openai-completions.js batches consecutive toolResult messages before emitting images (for (; j < transformedMessages.length && transformedMessages[j].role === "toolResult"; j++)) and emits a single user message with all accumulated images afterwards. messagesToCC() does not, which is why the same conversation works through the native provider path and breaks on /alpha/generate.
This is the only difference needed to fix it — see #84.
Why this is not a duplicate of #84
#84 contains the same root cause and the fix, but it is not merged and it was verified against Oh My Pi with Qwen/Qwen3.8-Flash. This report adds:
- a pi host reproduction (not OMP),
- a Command Code Go plan account, where the transport router forces
/alpha/generate instead of the Provider API,
- the observation that the Provider API path (
/provider/v1) is never reached on Go plans, so Go-plan users hit the broken converter unconditionally,
- confirmation that the bug is unrelated to the model — any non-
claude- model on the generate transport is affected.
If you prefer to keep tracking to #84, feel free to close this as a duplicate. The Go-plan transport routing is the part #84 does not cover.
Environment
|
|
| pi |
0.84.3 |
| pi-commandcode-provider |
0.6.4 |
| Node |
v22.23.1 |
| OS |
Linux x64 |
| Plan |
Go (/provider/v1 returns 403 upgrade_required) |
| Transport |
/alpha/generate (legacy) |
| Model |
deepseek/deepseek-v4.1-flash |
Workaround
Read images one per turn. Already applied #84 locally as a patch until it lands upstream.
Summary
On a Go plan account, the extension falls back to the legacy
/alpha/generatetransport (the Provider API answers403 upgrade_required). On that transport,messagesToCC()interleaves image parts between consecutive tool results:The gateway closes the tool turn as soon as it sees the
usermessage, socall Bis still unanswered and the request aborts with:The failure is deterministic: all 3 retries send the same body and fail identically. The session JSONL stays valid (every
toolCallhas exactly onetoolResult, no orphans, no duplicates) — only the serialized request is malformed.Minimal reproduction
/alpha/generate./tmp/a.png,/tmp/b.png).readcalls.Result:
Tool result is missing for tool call ...: server_error, stuck after 3 retries.Reading one image per turn succeeds every time in the same session. Reading two in one turn fails every time. Three would fail the same way.
Local verification of the cause
I ran the exact message array through
messagesToCC()fromsrc/converters.ts(0.6.4) using the real session context that failed:Then I applied the diff from #84 locally and re-ran the same context:
The malformed ordering is gone. Regression checks with the patched converter for 1 / 2 / 3 parallel image reads, text-only models (
allowImages: false), tool errors, and mixed text+image results all produce the expected shapes.Note:
pi-aialready does this correctlypi-ai/dist/api/openai-completions.jsbatches consecutivetoolResultmessages before emitting images (for (; j < transformedMessages.length && transformedMessages[j].role === "toolResult"; j++)) and emits a singleusermessage with all accumulated images afterwards.messagesToCC()does not, which is why the same conversation works through the native provider path and breaks on/alpha/generate.This is the only difference needed to fix it — see #84.
Why this is not a duplicate of #84
#84 contains the same root cause and the fix, but it is not merged and it was verified against Oh My Pi with
Qwen/Qwen3.8-Flash. This report adds:/alpha/generateinstead of the Provider API,/provider/v1) is never reached on Go plans, so Go-plan users hit the broken converter unconditionally,claude-model on the generate transport is affected.If you prefer to keep tracking to #84, feel free to close this as a duplicate. The Go-plan transport routing is the part #84 does not cover.
Environment
/provider/v1returns403 upgrade_required)/alpha/generate(legacy)deepseek/deepseek-v4.1-flashWorkaround
Read images one per turn. Already applied #84 locally as a patch until it lands upstream.