MoonshotAI / MoonshotAI/kimi-code
Session permanently bricked after provider rejects an image with a generic 400 ("Invalid request.") — media-strip recovery never engages
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 7.5k
- Forks
- 1.2k
- Avg merge
- 11h 53m
- Merged PRs (30d)
- 350
Description
Summary
When a provider rejects a request containing an image with HTTP 400 and a generic error
message (Invalid request.), the media-strip recovery introduced in 0.23.5 / 0.24.0 does
not engage, because isImageFormatError only matches a fixed set of message patterns.
The turn fails as non-retryable, the image stays in the conversation history, and every
subsequent request — including after kimi -r and brand-new prompts — fails with the
same 400. The session is permanently unusable.
This contradicts the stated intent of the existing mechanism ("recover an already-stuck
session by dropping the offending image and retrying, so one such image can no longer
make every later request fail" — 0.23.5 changelog).
Environment
- kimi-code 0.37.2 (native install), macOS arm64
- Provider: custom
openai-type provider (third-party OpenAI-compatible gateway),
modelkimi-k3, capabilities includeimage_in, thinking effortmax
Steps to reproduce
- Configure an
openai-type model that declaresimage_in, but whose backend rejects
image input with a generic 400 body, e.g.
{"error":{"code":"invalid_request","message":"Invalid request.","param":null,"type":"invalid_request_error"}} - In a session, let the agent read an image with
ReadMediaFile(two screenshots, in
this case), or paste an image. - The next request — the first one containing the image — fails:
provider.api_error: 400 Invalid request. - Send any further prompt, or resume with
kimi -r <session>: every request fails with
the same 400. The session never recovers.
Expected behavior
One rejected image must not brick a session. The client should detect the image-related
rejection and resend with the offending media stripped (as it already does for
unsupported image url-style errors), or at least offer a recovery path.
Actual behavior
Every turn fails with 400 Invalid request. (retryable: false), forever.
Session log excerpt:
2026-08-19T07:22:11.050Z WARN llm request failed turnStep=1.19 model=k3-flashflame errorName=APIStatusError errorMessage="400 Invalid request." statusCode=400
2026-08-19T07:28:40.978Z WARN llm request failed turnStep=2.1 model=k3-flashflame errorName=APIStatusError errorMessage="400 Invalid request." statusCode=400
2026-08-19T07:30:58.222Z WARN llm request failed turnStep=3.1 model=k3-flashflame errorName=APIStatusError errorMessage="400 Invalid request." statusCode=400
2026-08-19T07:40:37.260Z WARN llm request failed turnStep=4.1 model=k3-flashflame errorName=APIStatusError errorMessage="400 Invalid request." statusCode=400
2026-08-19T07:45:20.991Z WARN llm request failed turnStep=5.1 model=k3-flashflame errorName=APIStatusError errorMessage="400 Invalid request." statusCode=400
Root-cause analysis
I captured the provider-bound request with a local relay:
- The client's request is spec-compliant:
blobref:references are correctly resolved
to validdata:image/png;base64,...payloads (valid PNG bytes), placed in a synthetic
user message ("Attached media from tool result:"), with correct tool_call/tool_result
pairing. The 400 comes from the provider — this route does not accept image input at
all (controlled replays against the provider: identical request without images → 200;
with any image, even a 1x1 PNG or a remote URL → 400). - The recovery path in
packages/agent-core-v2/src/agent/llmRequester/llmRequesterService.tsonly strips media
whenisImageFormatError(raw)returns true
(packages/agent-core-v2/src/kosong/contract/errors.ts). Its pattern list
(unsupported image url|format|type,invalid image ..., etc.) does not match a bare
Invalid request., so the strip-and-resend never happens and the error propagates as
non-retryable.
Suggested fix: when a 400 is received and the projected request contains media parts,
attempt the media-strip resend even if the error message doesn't match the known patterns
(limited to one attempt), instead of failing the turn outright.
Secondary observation: kimi -p retries a deterministic 400 ten times
A kimi -p run against this bricked session issued 10 identical requests with exponential
backoff over ~3 minutes (captured via relay; all with X-Stainless-Retry-Count: 0, i.e.
agent-loop retries, not SDK retries). Interactive turns failed after a single attempt.
A 4xx invalid_request is deterministic and should not be retried.
Artifacts
I have a full debug session export (/export-debug-zip) and the captured
request/response pair; happy to share them privately if useful (they contain session
content, so I'd rather not attach them publicly).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with packages/agent-core-v2/src/agent/llmRequester/llmRequesterService.ts and trace its media recovery decision into packages/agent-core-v2/src/kosong/contract/errors.ts, including the existing image-error patterns. Confirm the behavior with a provider response like the documented generic 400; done means a media-bearing request has a bounded recovery path and deterministic invalid requests are not retried indefinitely.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 72/100