Skip to content

bug(provider-switch): switching OpenCode → Claude with a pasted image writes a foreign 'file' block and the Claude API rejects every later turn #999

Closed

Description

@Juliusolsson05

Expected behaviour

After an OpenCode → Claude switch, the projected Claude transcript contains only content blocks the Claude Messages API accepts. An image the user pasted in OpenCode arrives as a Claude image block ({type:'image', source:{type:'base64', media_type, data}}), or is dropped with a reported loss. The new Claude session keeps working.

Actual behaviour

The OpenCode file part is copied into the Claude transcript verbatim:

{"type":"file","mime":"image/png","filename":"clipboard","url":"data:image/png;base64,…(713,702 chars)",
 "source":{"text":{"value":"[Image 1]","start":119,"end":128},"type":"file","path":"clipboard"},
 "id":"prt_…","sessionID":"ses_…","messageID":"msg_…"}

Claude first answers twice with "I can't view images in this session" (the block is not an image to it), and as soon as the user pastes the screenshot again the whole session fails with:

API Error: 400 messages.985.content.1: Input tag 'file' found using 'type' does not match any of the
expected tags: 'advisor_tool_result', …, 'document', 'image', …, 'text', …, 'tool_use', …

(recorded as an isApiErrorMessage assistant record, apiErrorStatus: 400, 2026-09-18 20:27:17Z, Claude Code 2.1.277). The invalid block stays in history, so every subsequent turn in that Claude session gets the same 400. The user's only way out was a switch back to OpenCode, which then hit #998.

Root cause (in agent-transcript-parser)

claudeMessageContent in src/claude/project/nativeResume.ts pushes { ...item.value } for every neutral image / document content item without checking that the value is a Claude-shaped block. The OpenCode decoder wraps its native file part as {kind:'image', value: part} and the Codex decoder wraps input_image items the same way, so both foreign shapes reach the Claude file unchanged. The other two projectors already convert in the opposite direction (codexImage, filePart); the Claude projector is the only one that emits a block the target's API is known to reject.

Impact

Any OpenCode → Claude or Codex → Claude switch of a conversation that contains a user image produces a Claude session that breaks on its next turn. Data-integrity risk: the user sees a working pane with full history and only discovers the corruption when they submit.

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

Environment

macOS 26.6, Electron 43.1.1, Claude Code 2.1.277, OpenCode source session with one clipboard image.

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