Skip to content

bug: message pipeline has no final empty-content guard, causing ValidationException on Bedrock and hard failures on other providers #23260

Closed as not planned

Description

@jpvelasco

Description

The message() pipeline in packages/opencode/src/provider/transform.ts applies a sequential series of transformation passes but has no final validation step. Multiple passes can each leave messages with content: [] or content: "", which providers reject with hard errors that permanently break the active session.

Expected Behavior

No message with empty content should ever reach a provider API.

Actual Behavior

Three transformation passes can each produce empty-content messages with no downstream guard:

Pass Location Produces
Pass 1 (Anthropic/Bedrock text filter) transform.ts:55-73 Guarded locally, but only for two providers
Pass 3 (Anthropic tool reorder flatMap) transform.ts:115-126 content: [] if the non-tool split half was all-empty text parts
Pass 5 (interleaved reasoning filter) transform.ts:178-211 content: [] when the assistant message contained only reasoning parts
unsupportedParts() transform.ts:267-303 content: [] if all parts were unsupported modality

On Bedrock this produces:

ValidationException: messages: text content blocks must be non-empty

This error permanently breaks the session.

Steps to Reproduce

  1. Use a Bedrock model with interleaved reasoning (e.g. amazon-bedrock/zai.glm-4.7)
  2. Send a message that triggers the reasoning filter, producing an assistant message where all content is reasoning parts
  3. The reasoning filter strips all reasoning parts, leaving content: []
  4. Bedrock rejects the next request with ValidationException

Related reports: #15715, #16332, #22364, #17705

Environment

  • Provider: Amazon Bedrock (also affects other providers)
  • Model: Any model with interleaved reasoning capability, or any Anthropic model on Bedrock

Additional Context

The root cause is architectural: each transformation pass has its own (or no) local guard, and passes added later never see the guards from earlier passes. A single O(n) filter at the end of message() would catch all current and future cases.

Related open PRs: #17396, #17712, #17742

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on Apr 18, 2026
  2. github-actions commented on Apr 18, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of (or closely related to) existing open issues. Please check:

    The issue itself references these as "related reports", but they appear to describe the same underlying problem. If this issue is intended as a root-cause analysis or umbrella fix rather than a new report, please clarify that distinction in the issue body so maintainers can triage accordingly.

  3. jpvelasco commented on Apr 18, 2026

    @jpvelasco
    Author

    This is an umbrella root-cause issue, not a duplicate report. The referenced issues (#15715, #16332, #17705, #22364) are all symptoms of the same architectural gap: the message() pipeline has no final empty-content validation step, so any transformation pass that produces content: [] or content: "" silently passes through to the provider.

    Each of those issues was filed against a specific provider or scenario. This issue identifies the common cause across all of them and tracks PR #23259, which adds a single guard at the end of the pipeline that fixes all current and future cases in one place.

  4. removed
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  5. Oxygen56 commented on Jun 24, 2026

    @Oxygen56

    Also reproduces with DeepSeek provider. Adding reproduction details:

    Error:

    400 Bad Request: messages.55: all messages must have non-empty content
    

    Reproduction:

    1. Configure OpenCode with a DeepSeek provider (e.g., deepseek-chat)
    2. Build up a conversation context with 50+ messages (system prompt + multi-turn tool use + many user/assistant exchanges)
    3. The DeepSeek API rejects with messages.N: all messages must have non-empty content (observed at N=47 and N=55 on different runs)

    Environment:

    • OpenCode version: 1.17.9 (CLI v0.3.28)
    • Provider: DeepSeek (deepseek-chat)
    • Platform: macOS (darwin)

    Trigger condition: A long-running agent task where context grows to 50+ messages (including a 7800+ char comment with markdown tables). The error occurs consistently once the message count exceeds ~47.

    Note: Unlike Bedrock which gives ValidationException, DeepSeek returns a 400 with a specific message index — which is actually more useful for debugging since it pinpoints exactly which message in the array is empty.

    This confirms the same root cause (no final empty-content guard in the message pipeline) affects at least three providers: Bedrock, Anthropic, and DeepSeek. The fix proposed in the issue description — a single O(n) filter at the end of message() — would fix all of them.

  6. added 2 commits that reference this issue on Jun 24, 2026
    64b7f9b
    54fab33
  7. github-actions commented on Aug 24, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions