Skip to content

Empty message content causes Anthropic API errors during compaction #6446

Closed

Description

@Melodeiro

Description

During compaction, Anthropic API returns:

messages.9: all messages must have non-empty content except for the optional final assistant message

Root Cause Analysis

After investigating the storage data, found the following message structure in the problematic session:

[0] user     | parts: text
[1] assistant | parts: step-start, tool (completed), step-finish  ← NO TEXT
[2] assistant | parts: step-start, tool (completed), step-finish  ← NO TEXT, consecutive
[3] assistant | parts: step-start, tool (completed), step-finish  ← NO TEXT, consecutive
[4] assistant | parts: step-start, text, step-finish
[5] user     | parts: text
...

Key observations:

  1. Messages [1], [2], [3] are consecutive assistant messages with only tool calls (no text)
  2. step-finish part type is not handled in toModelMessage() (lines 480-537)
  3. The filter on line 541 passes these messages because they have tool-* parts (not just step-start)

The Conversion Pipeline

In packages/opencode/src/session/message-v2.ts:

// Line 541
return convertToModelMessages(result.filter((msg) => msg.parts.some((part) => part.type !== "step-start")))

The convertToModelMessages from AI SDK (ai package) then converts UIMessage[] to ModelMessage[].

Hypothesis: When convertToModelMessages processes consecutive assistant messages with tool calls, it may:

  1. Create synthetic user messages for tool results
  2. These synthetic messages might end up empty in certain edge cases
  3. Or the merging logic creates an empty content array

Affected Code

  • packages/opencode/src/session/message-v2.ts:421-542 — toModelMessage() function
  • Called from packages/opencode/src/session/compaction.ts

Questions

  1. How does convertToModelMessages handle consecutive assistant messages with tool calls?
  2. Should toModelMessage() merge consecutive assistant messages before passing to convertToModelMessages?
  3. Should there be validation after convertToModelMessages to filter out empty messages?

Reproduction

Session with multiple consecutive assistant messages containing only tool calls (no text parts) followed by compaction trigger.

OpenCode version

1.0.209

Steps to reproduce

No response

Screenshot and/or share link

No response

Operating System

Xubuntu 22.04

Terminal

ghostty

Activity

  1. github-actions commented on Dec 30, 2025

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Feel free to ignore if none of these address your specific case.

  2. rekram1-node commented on Dec 30, 2025

    @rekram1-node
    Contributor

    Can u share a session u got this error w/?

    Simply run this:

    opencode export > session.json

    And send the session

  3. zitongcharliedeng commented on Dec 31, 2025

    @zitongcharliedeng

    +1 I've had to stop using Opus in opencode because of this :(

  4. rekram1-node commented on Dec 31, 2025

    @rekram1-node
    Contributor

    Can u plz send a session :)

  5. zitongcharliedeng commented on Dec 31, 2025

    @zitongcharliedeng
  6. rekram1-node commented on Jan 6, 2026

    @rekram1-node
    Contributor

    This should be fixed

  7. BouquetAntoine commented on Jan 9, 2026

    @BouquetAntoine

    Hi, i don't know if this is deployed or not but got the same error on last (1.1.8) version, session not shareable as used under WSL and "copied to clipboard" = no copy. I'm using Azure Inference for Anthropic "azure-anthropic": { "name": "Azure AI Foundry (Anthropic)", "npm": "@ai-sdk/anthropic", "options": { "baseURL": "https://XXXXXXXXXXXXXX-resource.services.ai.azure.com/anthropic/v1", "headers": { "anthropic-beta": "claude-code-20250219,interleaved-thinking-2025-05-14,fine-grained-tool-streaming-2025-05-14,token-efficient-tools-2025-02-19,context-1m-2025-08-07,context-management-2025-06-27,tool-search-tool-2025-10-19,tool-examples-2025-10-29" } }, "models": { "claude-opus-4-5-20251101": { "id": "claude-opus-4-5-20251101", "name": "Claude Opus 4.5 (200K)", "tool_call": true, "attachment": true, "reasoning": true, "temperature": true, "limit": { "context": 200000, "output": 64000 }, "price": { "input": 15, "output": 75 }, "options": { "thinking": { "type": "enabled", "budgetTokens": 32000 } } }, "claude-sonnet-4-5-2": { // not an error, my deployment name on Azure "id": "claude-sonnet-4-5-2", "name": "Claude Sonnet 4.5 (1M)", "tool_call": true, "attachment": true, "reasoning": true, "temperature": true, "limit": { "context": 1000000, "output": 32000 }, "price": { "input": 6, "output": 22.5 }, "options": { "thinking": { "type": "enabled", "budgetTokens": 16000 } } } } }

    Conv was like :
    [assistant] sub agent : finished
    [assistant thinking] explore task finished, starting dev task (sub agent)
    assistant were then starting the new sub agent => all messages must have non empty content

    Note: Azure Anthropic is the exact same implementation than Anthropic API, same headers, same way to send API key, ...

  8. sadmansk commented on Mar 31, 2026

    @sadmansk

    I am also still encountering this in version 1.3.2, although using nemotron nano

      ┃  litellm.BadRequestError: OpenAIException - {"error":{"message":            
      ┃  "request: Value error, message content cannot be empty","type":            
      ┃  "BadRequestError","code":400}}. Received Model Group=nvidia/nvidia/        
      ┃  Nemotron-3-Nano-30B-A3B                                                    
      ┃  Available Model Group Fallbacks=None                                       
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions