You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
core: request accumulates 52 images, exceeds provider max of 50 #47721
Upstream request fails with [invalid_request_error] request contains 52 images, exceeding the maximum of 50 allowed per request after iterating on image assets in a single session.
Active plugins: none found (no .opencode/plugin or .opencode/plugins; config has minimax provider + exa remote MCP only)
Reproduction
In one session, iteratively build HTML visuals rendered via headless Chrome.
Have the agent read PNG/GIF outputs after each iteration (hero-banner.png, anatomy-eta.png, decision-2am.png, plus GIF previews).
After ~10-15 image reads across turns, any new request fails.
Error: Error from provider (Console): Upstream request failed: [invalid_request_error] request contains 52 images, exceeding the maximum of 50 allowed per request
Expected Behavior
Old image attachments should be compacted, dropped, or blocked before hitting the provider cap — or at least warn with a way to continue text-only.
Actual Behavior
Request hard-fails at 52/50 images. Session is stuck: can't send even text-only prompts without hitting the image count.
Additional Context
Earlier in same session also saw: Unable to process image(s). Mouse mode is currently enabled, which limits image messages to one image per message.
Workflow: Substack visual iteration (1600px HTML -> Chrome screenshot -> read PNG). No manual image uploads, only agent read tool outputs.
Frequency: consistent once cap is hit; blocks the session.
Workaround tried: none yet — reporter asked agent to stop sending images. Proposed: new session / text-only mode.
We ran into this on 2.0 as well and traced the exact core mechanism on current dev:
boundImages() in packages/core/src/session/model-request.ts triggers only on bytes (>25 MiB). It enforces no count check, so 51 small screenshots (e.g. 50 KB each) still get sent and trigger provider 400 errors.
packages/ai/src/provider-error.ts doesn’t classify image-count errors as context overflow, so automatic recovery never runs.
When core does remove an image, its placeholder is a blind stub without the file path, so the model cannot re-read it.
Proposed fix:
Add an image count ceiling to boundImages().
Keep the file path in the removal placeholder so the model has actionable recall via read.
Add image-count patterns to provider-error.ts so recovery kicks in.
We have a working standalone implementation in opencode-prune-images and have this patch ready if you would like a core PR.
Thanks for the detailed report. I reproduced this on @opencode/[email protected] with a simulated model.
Steps:
The agent read about 50 tiny PNGs (a few bytes each, so nowhere near the image-size limit) one at a time over two prompts.
Each model request carried every image read so far: 1, 2, … 50, then 51. OpenCode only limits the total size of images in a request, not how many there are.
A mock provider answered the 51-image request with the error from your report. OpenCode showed Error: Upstream request failed: [invalid_request_error] request contains 51 images, exceeding the maximum of 50 allowed per request and didn't compact or drop old images, so the session can't recover. The screenshot below shows it.
In an earlier run that stopped at 50 images, a follow-up text-only prompt was sent with all 50 images again. That's why plain text prompts fail once a session is over the limit.
I ran out of time before re-running this on the latest dev, but the code there still has no count limit. Open PR #59303 targets this problem. Related: #55060 (same problem on 2.0, closed as a duplicate of this issue), #47487 and #39677 (the same problem on v1), #48781 (the same thing through PDF pages).
Reproduction script (drive.ts)
// Repro for #47721: v2 sends every image in history to the model, with no count cap.// 52 tiny PNGs (~70 bytes each, far below the 25 MiB byte bound) are read one by one,// then a plain text prompt is sent. We count the images carried in each model request.import{Effect,Stream}from"effect"import{Llm,OpenCodeDriver}from"opencode-drive"constN=Number(process.env.N??56)// 1x1 PNGconstPNG=Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==","base64",)constfiles: Record<string,Uint8Array>={}for(leti=0;i<N;i++)files[`shots/shot-${i}.png`]=PNGconstcountImages=(req: unknown)=>{consts=JSON.stringify(req)return(s.match(/"type":"image_url"/g)??[]).length}constcounts: number[]=[]letshot=0letturn2=false// Wait on the model requests themselves (the TUI's matches RPC stalls with many images on screen).constwaitRequests=(n: number)=>Effect.gen(function*(){for(leti=0;i<600&&counts.length<n;i++)yield*Effect.sleep(250)})exportdefaultOpenCodeDriver.use({
...(process.env.OC_DEV ? {opencode: {dev: process.env.OC_DEV}} : {}),project: {git: true, files },tui: {recording: true},// Give the simulated model image input, like the Console models in the report.config: {providers: {simulation: {models: {"gpt-sim-model": {capabilities: {tools: true,input: ["text","image"],output: ["text"]}}}}},},},({ ui, llm })=>Effect.gen(function*(){yield*llm.serve((request,index)=>{constn=countImages(request)if(index===3&&process.env.DUMP)require("fs").writeFileSync("/tmp/opencode/req3.json",JSON.stringify(request,null,1))// Stand-in for the provider's documented 50-image cap: reject like Console does.if(n>50)returnStream.make(Llm.raw({error: {type: "invalid_request_error",message: `Upstream request failed: [invalid_request_error] request contains ${n} images, exceeding the maximum of 50 allowed per request`}}),)counts.push(n)console.log(`[repro] model request #${index}: ${n} image(s)`)// Turn 1 reads shots 0-29, turn 2 reads shots 30-51 (stays under the per-turn step limit).if(shot<N&&!(shot===30&&!turn2)){consti=shot++returnStream.make(Llm.toolCall({index: 0,id: `call_${i}`,name: "read",input: {path: `shots/shot-${i}.png`}}),Llm.finish("tool-calls"),)}returnStream.make(Llm.text(`REQUEST_${index}_CARRIED_${n}_IMAGES`))})yield*ui.submit("Read every screenshot in shots/ one at a time")yield*waitRequests(31)turn2=trueyield*ui.submit("Now read the rest")yield*waitRequests(N+2)yield*Effect.sleep(3000)yield*ui.screenshot("after-52-reads")yield*ui.submit("Text only: stop sending images and just say hi")yield*Effect.sleep(8000)yield*waitRequests(N+4)yield*ui.screenshot("text-only-prompt")console.log(`[repro] image counts per request: ${counts.join(",")}`)console.log(`[repro] max images in one request: ${Math.max(...counts)} (provider cap in report: 50)`)}),)
Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.
Activity
github-actions commentedon Sep 7, 2026
This issue might be a duplicate of existing issues. Please check:
Sugamsss commentedon Oct 14, 2026
We ran into this on 2.0 as well and traced the exact core mechanism on current
dev:boundImages()inpackages/core/src/session/model-request.tstriggers only on bytes (>25 MiB). It enforces no count check, so 51 small screenshots (e.g. 50 KB each) still get sent and trigger provider 400 errors.packages/ai/src/provider-error.tsdoesn’t classify image-count errors as context overflow, so automatic recovery never runs.Proposed fix:
boundImages().read.provider-error.tsso recovery kicks in.We have a working standalone implementation in
opencode-prune-imagesand have this patch ready if you would like a core PR.opencodejr commentedon Oct 28, 2026
Thanks for the detailed report. I reproduced this on
@opencode/[email protected]with a simulated model.Steps:
readabout 50 tiny PNGs (a few bytes each, so nowhere near the image-size limit) one at a time over two prompts.Error: Upstream request failed: [invalid_request_error] request contains 51 images, exceeding the maximum of 50 allowed per requestand didn't compact or drop old images, so the session can't recover. The screenshot below shows it.In an earlier run that stopped at 50 images, a follow-up text-only prompt was sent with all 50 images again. That's why plain text prompts fail once a session is over the limit.
I ran out of time before re-running this on the latest
dev, but the code there still has no count limit. Open PR #59303 targets this problem. Related: #55060 (same problem on 2.0, closed as a duplicate of this issue), #47487 and #39677 (the same problem on v1), #48781 (the same thing through PDF pages).Reproduction script (drive.ts)
Run it with opencode-drive:
opencode-drive run ./drive.ts. It drivesopencodefrom yourPATH; setOC_DEVto the path of an OpenCode checkout to run that from source instead.iEsclyOrg commentedon Nov 12, 2026
Hey Im having the same issue. I tried compacting but it didn't worked. Currently on opencode latest version for windows desktop(2.2.7)
I'm using api from Nvidia model: minimax m3.1 flash