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
bug(operations): shrink ladder cannot remove message attachments and never revisits protected turns, so one pasted image makes a conversation unfittable #28
shrinkConversationToBudget throws ConversationUnfittableError only when no complete user turn can be made to fit — that is, when the newest turn's authored text alone exceeds the budget. Attachment payload (an image, document or opaque content item inside a message) is consumed input, like a tool output, and the ladder should be able to replace it with a placeholder before giving up. Recent-turn protection is a preference for keeping "what I was just doing" intact, not a reason to refuse a switch.
Actual behaviour
A real Claude transcript (1,100 entries, 16 safe boundaries, 2,458,176 estimated characters) against the 288,000-character OpenCode budget:
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, estimatedCharactersAfter: 549785 }
The newest user turn is [text(15), image(549,526)] — a 15-character prompt and a pasted PNG. Two turns earlier another user message carries an opaque 713,997-character block. Rungs 2 and 3 skip both (protected turns); rung 4 drops every earlier turn and the suffix is still 549,620; the fallback keeps that turn whole and throws.
Reproduction is deterministic: decode the transcript with decodeClaudeConversation, call planConversationContext(conversation, 'opencode', 288_000, { allowSourceTurns: false }).
Root cause
No rung touches message content. clearToolResults and trimToolInputs only look at tool-result and tool-call entries. A message whose estimate is dominated by an image / document / opaque content item can only be dropped whole, and the newest one cannot be dropped at all.
dropOldestTurns' final fallback ("keep the last complete turn whole and say so") is reached with the recent-turn protection still in force, so the payload inside those turns is never reclaimed. The design already relaxes the protection for single-turn conversations on exactly this argument ("declaring such a session unfittable while nine tenths of it is stale tool output would be a worse answer than the evidence supports"); the same argument applies whenever the protected suffix alone exceeds the budget.
Proposed fix
New rung between clearing tool results and trimming inputs: replace non-text content items in messages with a text placeholder, oldest first, same protection and net-savings guard as rung 2. Report clearedAttachments / clearedAttachmentChars.
When the drop rung cannot fit any boundary, re-run the clearing rungs with the protection lifted on the pre-drop conversation and drop again; throw only if that still fails. Report liftedRecentTurnProtection.
Activity
fix(operations): resolve review findings on the protection lift and i…