Skip to content

bug(provider-switch): quota-independent switch fails with ConversationUnfittableError when a recent turn carries a pasted image #998

Closed

Description

@Juliusolsson05

Expected behaviour

Switching a Claude agent to OpenCode with the default (quota-independent) policy always succeeds when the conversation has any complete user turn that fits the target budget. The shrink ladder exists precisely so an over-budget history is trimmed rather than refused; a pasted screenshot in the newest turn should be dropped with a "1 image omitted" line in the toast, not abort the switch.

Actual behaviour

Two consecutive attempts (2026-09-18 20:28:21Z and 20:30:12Z, app run 2026-09-18T20-21-26-461Z-main-76278-c6757d, build ad9b2697 main) both failed inside the session:switch-provider handler:

ConversationUnfittableError: Conversation cannot fit the target budget of 288000 characters:
the smallest complete suffix the ladder can produce is 549785 characters.
  report: { clearedResults: 256, clearedChars: 664449, trimmedInputs: 3, trimmedChars: 24773,
            droppedEntries: 1079, droppedTurns: 15, estimatedCharactersBefore: 2458176,
            estimatedCharactersAfter: 549785, budgetCharacters: 288000 }

288,000 is the OpenCode target budget (128k tokens × 0.9 × 2.5 chars/token).

Evidence (source transcript decoded with the parser, 1,100 entries, 16 safe boundaries)

entry line kind estimate content
1070 992 user message 714,226 129-char prompt + opaque block of 713,997 chars (an OpenCode {type:'file', mime:'image/png', url:'data:…'} part copied into the Claude transcript by an earlier OpenCode → Claude switch, see #999)
1079 1000 user message 549,620 15-char prompt This [Image #1] + image block of 549,526 chars (base64 PNG pasted via the composer)
1080–1099 1001–1020 opaque 0 attachments, api_error, turn_duration

Suffix costs from the last boundaries: from entry 1055 = 1,294,156; from 1070 = 1,280,149; from 1079 = 549,620. Every rung ran: 256 tool outputs cleared, 3 inputs trimmed, 15 turns dropped. The one thing left, the newest user turn, is 99.98 % one image, and no rung can touch message content. The ladder then keeps "the last complete turn whole" and throws.

Impact

Root cause (in agent-transcript-parser)

  1. The shrink ladder has no rung for image / document / opaque content items inside messages, so a user turn dominated by an attachment can never shrink.
  2. Rungs 2 and 3 honour recent-turn protection and the drop rung's final fallback keeps the last turn whole and throws, so payload inside the protected turns is never reclaimed even when that is the only thing standing between the conversation and the budget.

Fixed by the parser change tracked in Juliusolsson05/agent-transcript-parser#28 and landed here by the submodule bump PR.

Environment

macOS 26.6 (Darwin 25.6.0), Electron 43.1.1, Node 24.18, Claude Code 2.1.277, OpenCode target on a 128k profile.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions