DeepSeek Harness tall screenshot 3000px: every turn 400
After one 390×6400 full-page screenshot enters a session, the user does nothing, and the next turn and the one after both fail with the same 400: At least one of the image dimensions exceed max allowed size for many-image requests: 3000 pixels. The image is not corrupt, and this turn's upload did not fail — that picture is already in history, and every request re-encodes it, so each turn is rejected at the same position (#11511). The timeline makes it clearest.
DeepSeek Harness: Before: how long a portrait image gets projected
Projection has two steps: scale on the token grid, then clamp the long side to 4096. Wide images are squeezed inside the limit, while full-page portrait images land exactly on 4096.
- The first step is token-grid scaling with an upper bound of
MAX_IMAGE_TOKENS = 1024. - The second clamps the long side to
REQUEST_IMAGE_MAX_DIMENSION. - The three data points the community measured:
| Source size | After grid projection | Actually sent (cap 4096) |
|---|---|---|
| 1440×3900 | 755×2044 | 755×2044 |
| 520×6400 | 336×4130 | 333×4096 |
| 390×6400 | 293×4816 | 250×4096 |
- A wide image like 1440×3900 projects far below 3000, so it is fine.
- The last two are clamped to 4096 — a number that can never satisfy a 3000 limit.
That explains why the failure appears "only after certain screenshots": not every image goes over, only full-page portrait ones do.
DSH plugin: When: why the whole request is rejected
Anthropic Messages limits each side of a multi-image request to 3000 pixels, and the rejection is request-wide; one offending image ends the turn.
- Read the error index:
messages.9.content.88.image.source.base64.datapoints at a historical image, not this turn's upload. - Check the constant:
REQUEST_IMAGE_MAX_DIMENSION = 4096inpackages/llm/llm-deepseek/src/request-pricing.ts. - Read the comment: it calls 4096 "the provider's per-side limit when carrying 15 or more images", while the endpoint's 400 explicitly says 3000 — both cannot be true.
- The result: the adapter projects a 4096 long side, the endpoint judges by 3000, the turn returns 400, and nothing from it is usable.
The community recomputed against master ce7073d5 (351 commits ahead of dsh-v0.2.2-alpha.2) and confirmed the clamp fires and still sends 4096 — this is code arithmetic, not an estimate.
DeepSeek Harness: After: history images are re-encoded every turn, so every turn fails
The encoder and the meter share the single resolveRequestImageTarget target, so every retained image in history is re-projected to the same size on every request.
images.tscallsresolveRequestImageTargetto produce the bytes actually sent.- In other words, the error is not made "at upload time"; it is regenerated at 4096 on every request.
- So as long as that image stays in history, every turn carries a 4096-long-side image.
- Retrying does not help, rewording does not help, and even a turn with no new images does not help.
- The only two ways back are removing that image from history or starting a new session.
The fix turns the cap into a per-model declared field: a capability entry can set imageMaxDimension, and without it the value stays 4096, so DeepSeek routes and existing configs are unaffected. With 3000 declared, 390×6400 projects to 183×3000 and the request succeeds. The adapter package passes 176 tests. The patch is on a community branch and not merged.
DSH Plugin: The other timeline: a relay pricing base64
The same "an image drags the session down" symptom has a completely different mechanism: a gateway prices base64 as ordinary text at about 1 token per raw byte, while the local meter records only 55-59 tokens per image.
- Look at the symptom: the error is
Input exceeds the context limit (1048568 tokens), not a size violation. - Look at the cost gap: for the same 5 images, Anthropic's public formula would charge about 6,860 tokens, while this route charged 1,001,062 — 146x.
- Look at the meter:
estimateStructuralBlock()counts only the attachment reference JSON for image blocks, 55-59 tokens each, against a real ~1,001,062, a gap of about 3,500x. - Look at the consequence: the dashboard shows a surface of only about 60k tokens while the real usage is 95.5% of the limit, so compaction never selects the images.
- Look at the trigger scope: after narrowing it down, the community confirmed the inflation happens only when "the gateway converts Anthropic format to OpenAI format and the image is inside a
tool_result" — andread_imagehappens to put images in tool_result. - Control experiment: the same 106,973-byte PNG costs 93,775 tokens inside a tool_result, but only 1,242 in a user message on another image.
This timeline has a side finding: when a route declares no window, it uses defaultContextWindow = 262144 while the real upstream limit is 1,048,568 — a 4x gap. Declaring defaultContextWindow: 1048576 in the profile for that route switches the running session on its next request, with no restart needed.
DeepSeek Harness: One-minute check: tell the two mechanisms apart
Two keywords in the error body are enough: 3000 pixels means the size problem, context limit means the pricing problem.
- Copy the full error and search for
3000 pixels; a hit means the size path. - Search for
Input exceeds the context limit; a hit means the pricing path. - Then check whether history contains
read_imageor full-page screenshots; either class can appear. - Both hitting at once is rare, but it can stack when a relay both converts protocols and enforces a size limit.
DSH plugin: Stop the bleeding: three cost-ordered actions
Identify the mechanism first, then work through "switch protocol → switch session → wait for the patch".
- Switch protocol or model: on relays, move to
openai-responsesor a Claude route so images are no longer priced as base64. - Send an ordinary message, then compact: after switching routes, send one short message to refresh
request/header, then run/compact; compacting directly often still computes from the old header. - Declare the route window: add
defaultContextWindow: 1048576in the profile so the dashboard judges by the real limit and avoids useless compaction. - Remove that image: for the long-screenshot case, drop the message containing it from history or start a new session.
- Wait for the per-model size patch: once the
imageMaxDimensionfield is merged, declaring 3000 on Messages routes fixes it at the root.
Cautions
Read the error keywords before touching any image or route.
- Do not look only at this turn's input. The error index points at the Nth image in history, not the one just uploaded.
- Do not conflate the two 400s. A size violation is
invalid_request_error; a context overflow isInput exceeds the context limit. - Do not count on image/offload to save you. It triggers only above 20 MiB of base64 bytes, while what is really burning is tokens.
- Do not use full-page portrait screenshots as evidence. Crop or use wide crops when you need a screenshot, to reduce the chance of triggering this.
- Refresh the header after switching protocols. The old header's pricing and window keep compaction broken.
If your 400 carries a messages.N.content.M index and mentions 3000, it is the first mechanism; if it mentions context limit and history contains read_image, check the second mechanism's pricing.See DeepSeek Harness image count limit 400: a session fix for the related case.
FAQ
The 400 comes from the Anthropic Messages many-image limit that DeepSeek Harness runs into: each side may be at most 3000 pixels, and one offending image rejects the whole request. The adapter constant REQUEST_IMAGE_MAX_DIMENSION is still 4096, so a tall screenshot is compressed to 4096 rather than 3000 and is guaranteed to be rejected.
Deleting one screenshot only helps for a turn because DeepSeek Harness re-encodes every retained image in history to the same target size on every request. As long as one image projects above 3000, every later turn carries it, which is what makes it look like one image dragging down the whole session.
Only tall images break in DeepSeek Harness: wide images are usually fine, since 1440×3900 projects to 755×2044, below the limit. Full-page portrait images break: 520×6400 projects to 336×4130 and is then clamped to 333×4096, and 390×6400 behaves the same. Full-page phone screenshots hit it most often.
No, DeepSeek Harness runs into a different mechanism here: a relay gateway prices image base64 as ordinary text (about 1 token per raw byte) while the local meter counts attachment reference length at 55-59 tokens per image, a roughly 3,500x gap, so compaction never selects the images.
To make the session usable again without starting a new one, switch protocol or model in DeepSeek Harness, send one ordinary message to refresh request/header, then run /compact. The community verified this restores the session; compacting directly often fails because the old header still prices and windows by the old values.
Related Terms
- many-image request limit
- The many-image request limit is the per-side pixel ceiling Anthropic Messages applies to requests carrying multiple images, at most 3000 pixels per side. One image over it rejects the entire request with an invalid_request_error.— https://github.com/deepseek-ai/deepseek-harness/discussions/11511
- REQUEST_IMAGE_MAX_DIMENSION
- REQUEST_IMAGE_MAX_DIMENSION is the constant in the DeepSeek adapter that caps the long side of request images, currently 4096. Its comment calls it the provider's multi-image limit, but the Messages endpoint actually allows only 3000.— https://github.com/deepseek-ai/deepseek-harness/discussions/11511
- resolveRequestImageTarget
- resolveRequestImageTarget is the DeepSeek Harness function that projects a source image to the request target size, first scaling on the token grid and then clamping the long side to the cap. The encoder and the meter share this target, so historical images are re-projected every turn.— https://github.com/deepseek-ai/deepseek-harness/discussions/11511
- image/offload
- image/offload is the image offloading mechanism that triggers only when accumulated base64 payload exceeds a byte budget. Token-priced overflow never triggers it, so on these routes it never takes effect.— https://github.com/deepseek-ai/deepseek-harness/discussions/11036
Sources
- #11511 — a tall screenshot makes every later turn fail on Messages endpoints· deepseek-ai (GitHub Discussions)
- #11036 — when a relay prices base64 as text, images cost about 3,500x more than the token meter reports· deepseek-ai (GitHub Discussions)
- deepseek-ai/deepseek-harness (source repository)· GitHub