Repository navigation
Description
[Bug]: qoder-cn rejects every image with 400 unsupported_input_modality, although qoderclicn accepts images and the model reads them correctly
Client or integration
Codex App
Area
Provider adapter
Summary
Any qoder-cn request whose conversation contains an image is refused before the CLI is ever invoked:
{
"status": 400,
"error": {
"message": "Qoder image input is not enabled because the CLI provider route has no verified multimodal contract.",
"type": "invalid_request_error",
"code": "unsupported_input_modality"
}
}Three problems with that:
- The premise is false for this CLI.
qoderclicn1.1.70 accepts images on both surfaces OpenCodex could use, and the models read them correctly (reproduction below). - The guard is region-scoped, not capability-scoped.
src/adapters/qoder/adapter.ts(runTurn) branches onresolveQoderProfile(provider.baseUrl)?.region === "cn"and refuses on the presence of anytype === "image"part — it never looks at the model. So it is impossible to opt in even with a model that visualises fine. - It is terminal and permanent. The error is
retryable: false, and because the image stays in the conversation history, every subsequent turn in that thread returns 400. The user's only escape is abandoning the thread — which is exactly what happened here (a PR-review thread was lost after one screenshot).
Expected: images route through unchanged, or — if some subset genuinely cannot be represented — the refusal should name the OpenCodex route limitation rather than implying the model cannot see, and should not poison the whole thread.
Reproduction
A. --attachment (agent-layer surface) — works
# 96x96 solid red PNG
qoderclicn -p --model DeepSeek-Flash --attachment .\red.png "What is the dominant colour? One word."
# → 红色
qoderclicn -p --model Qwen3.8-Flash --attachment .\red.png "What is the dominant colour? One word."
# → 红色B. Inline stream-json image block (the exact surface the adapter drives) — also works
# same argv the qoder adapter builds, with a base64 image part on stdin
qoderclicn -p --output-format stream-json --input-format stream-json `
--tools "" --strict-mcp-config --setting-sources "" --no-session-persistence `
--model DeepSeek-Flash
# stdin:
# {"type":"user","message":{"role":"user","content":[
# {"type":"image","source":{"type":"base64","media_type":"image/png","data":"iVBORw0..."}},
# {"type":"text","text":"What is the dominant colour? One word."}]}}
# → assistant text: 红色 (result: 红色)C. Same image through the proxy — refused
curl -s -X POST http://127.0.0.1:10100/v1/responses \
-H "Content-Type: application/json" \
-d '{"model":"qoder-cn/DeepSeek-Flash","input":[{"role":"user","content":[
{"type":"input_text","text":"dominant colour?"},
{"type":"input_image","image_url":"data:image/png;base64,iVBORw0..."}]}]}'
# → body: {"status":"failed","error":{"code":"unsupported_input_modality", ...}}
# the request log records HTTP 400 for the same calls from Codex (six consecutive attempts)(Note the secondary inconsistency: for a non-stream /v1/responses call the guard surfaces as HTTP 200 with status: "failed" in the body, while the streamed client calls are logged and returned as HTTP 400. Worth aligning while this is being touched.)
Text-only control on the same model returns status: completed, so the route itself is healthy.
D. With the region guard removed locally (throwaway build, for evidence only) the same request returns status: completed and the answer 红色. So the guard — not the CLI, not the model — is the failing component.
Scope of the evidence (so triage knows what is not covered)
Verified: single data:-URL / base64 image, two models (DeepSeek-Flash, Qwen3.8-Flash), CLI 1.1.70, both --attachment and inline stream-json.
Not verified: http(s) image URLs, multiple images in one turn, images returned from tool results, other models in the catalog. Those may be the reason the guard was added; if any of them genuinely fail, the fix could narrow the refusal to that subset instead of removing it.
Version
2.89.0
Operating system
Windows 11 (build 26200), win32 x64
Provider and model
qoder-cn / DeepSeek-Flash (also Qwen3.8-Flash)
Logs or error output
14:16:44 qoder-cn DeepSeek-Flash status=200 terminalStatus=failed durationMs=7
upstreamError: Qoder image input is not enabled because the CLI provider route
has no verified multimodal contract.
upstreamErrorCode: unsupported_input_modality
failureStage: protocol-prelude failureCause: payload-rejected
resendPermission: refused-futile
14:13:19..14:13:25 six consecutive 400s, identical, dur 28-59 ms (no CLI invocation)
Suggested fix
- Re-verify the contract against
qoderclicn≥ 1.1.70 — on this machine both surfaces carry images. - If it holds, drop the CN region guard (or gate it on a capability the profile actually verifies) rather than on region alone.
- Re-point
noVisionModelsforqoder-cn: it currently lists every catalogue entry (Qwen3.8-Max/Flash,Qwen3.7-Max/Plus/Flash), which is contradicted by the tests above. - If any subset must stay refused, make the message name the route limitation and keep it per-message — do not make the whole thread permanently unusable.
Config (redacted)
{
"providers": {
"qoder-cn": {
"adapter": "qoder",
"baseUrl": "https://qoder.cn",
"authMode": "key",
"noVisionModels": ["Qwen3.8-Max","Qwen3.8-Flash","Qwen3.7-Max","Qwen3.7-Plus","Qwen3.7-Flash"]
}
}
}Checks
- I searched existing issues and documentation (
unsupported_input_modality,Qoder CN,qodercn,image input; nearest are#7086,#2511,#7307, all unrelated). - I removed secrets, tokens, account details, request credentials, and personal data.
Activity
github-actions commentedon Oct 22, 2026
Automated translation bookkeeping — detected language: English.
Ingwannu commentedon Oct 22, 2026
Current dev
bf4eb1209672still explicitly rejects residual raw-image input on the CN profile before CLI invocation; the reported single-image results on qoderclicn 1.1.70 are useful new capability evidence. That refusal describes the currently unverified OpenCodex CLI route, not a determination that the underlying models cannot see. I am keeping this open and separate from the Windows dispatcher failure in #7518.A focused capability change should state the exact supported CLI/version/input contract and prove the existing shared projection with an injected CLI: data/base64 images, multiple images, tool-result images, history ordering/replay, cancellation and explicit refusal of unsupported references. The two reported model successes do not justify removing every model's sidecar classification or advertising arbitrary URL/history support. No live PAT/model invocation or local installed-runtime change was made here.
The HTTP-200/failed-body observation also needs its own JSON/SSE terminal regression before changing the shared response-status contract; it is not a successful model completion merely because the transport status is 200.
fix(qoder): forward Qoder CN images through shared coding-agent proje…
fix(qoder): forward Qoder CN images through shared coding-agent proje…
lidge-jun commentedon Oct 27, 2026
Fixed on
devby #7536 (squash merge1112f5b958797b0e49e6b9fc1bd838ac555b59b0).Qoder CN now forwards image input through the shared coding-agent projection instead of refusing it. Each image must be canonical base64 PNG data with a valid PNG signature and IHDR header, and at most 1 MiB once decoded; an image that fails these checks is refused before Qoder CLI discovery. Unsupported images in history keep their omission marker.
Closing as resolved on
dev; it ships in the next release.