Skip to content

Empty content blocks persist in database causing permanent Bedrock ValidationException (LiteLLM/openai-compatible proxies) #19309

@erichasinternet

Description

@erichasinternet

Description

OpenCode stores empty text blocks in the part and message database tables during streaming, causing permanent ValidationException errors when using AWS Bedrock via LiteLLM proxy (@ai-sdk/openai-compatible).

Once empty content blocks are stored in the database, the session becomes permanently broken - every subsequent API call fails with Bedrock validation errors, even after restarting OpenCode.

Root Cause

  1. Empty parts created during streaming: processor.ts:72 creates text/reasoning parts with text: ""
  2. Stored immediately without validation: Session.updatePart() saves them to database
  3. Filtering happens too late: transform.ts:52-72 only filters at API request time
  4. Filter doesn't cover all providers: Only applies to @ai-sdk/anthropic and @ai-sdk/amazon-bedrock, not @ai-sdk/openai-compatible

Evidence from Database Analysis

After encountering this issue, I analyzed my OpenCode database:

# Corrupted parts table
cursor.execute("SELECT COUNT(*) FROM part WHERE data LIKE '%\"text\":\"\"%'")
# Result: 26 empty parts

# Corrupted messages table  
cursor.execute("SELECT COUNT(*) FROM message WHERE data LIKE '%\"content\":[]}%'")
# Result: 2 empty messages

Error Message

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

Reproduction

  1. Use OpenCode with AWS Bedrock via LiteLLM proxy (openai-compatible provider)
  2. Engage in multi-turn conversation with tool calls
  3. Empty text parts get created during streaming (from text-start events with no deltas)
  4. These persist in database
  5. Session permanently broken with ValidationException on subsequent turns

Related Issues & PRs

Gap: Existing PRs Don't Address Recovery

PR #17742 provides excellent prevention by filtering empty parts before they're stored, but doesn't help users with already-corrupted databases.

This issue tracks the recovery aspect:

  • How to detect corrupted databases
  • How to repair existing corruption
  • Runtime filtering to handle legacy corrupted data

Proposed Solution

Two-part approach:

  1. Prevention (PR fix(opencode): filter empty text content blocks for all providers #17742 or similar):

    • Universal empty content filtering for all providers
    • Filter at message construction time in message-v2.ts
  2. Recovery (this issue):

    • Add defensive filtering when loading parts from database
    • Filters out empty text/reasoning parts at read time
    • Automatically repairs corrupted sessions without manual intervention

Workaround: Database Repair Script

For users with corrupted databases, I've created a Python repair script:

View repair script (click to expand)

```python
#!/usr/bin/env python3
"""
OpenCode Session Repair Script
Fixes empty content blocks that cause Bedrock validation errors
"""
import sqlite3
import json
import shutil
from datetime import datetime
from pathlib import Path
import sys

DB_PATH = Path.home() / '.local/share/opencode/opencode.db'

def backup_database() -> Path:
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
backup_path = Path(f"{DB_PATH}.backup.{timestamp}")
shutil.copy2(DB_PATH, backup_path)
print(f"✅ Database backed up to: {backup_path}\n")
return backup_path

def fix_session(conn: sqlite3.Connection, session_id: str) -> tuple[int, int]:
cursor = conn.cursor()

# Fix messages table
cursor.execute("SELECT id, data FROM message WHERE session_id = ?", (session_id,))
fixed_msg_count = 0

for msg_id, msg_data in cursor.fetchall():
    try:
        data = json.loads(msg_data)
        content = data.get('content', [])
        needs_fix = False
        
        if isinstance(content, list) and len(content) == 0:
            data['content'] = [{"type": "text", "text": "."}]
            needs_fix = True
        elif isinstance(content, list):
            for i, block in enumerate(content):
                if block.get('type') == 'text' and block.get('text', '').strip() == '':
                    data['content'][i]['text'] = '.'
                    needs_fix = True
        
        if needs_fix:
            cursor.execute("UPDATE message SET data = ? WHERE id = ?", 
                         (json.dumps(data), msg_id))
            fixed_msg_count += 1
    except (json.JSONDecodeError, KeyError, AttributeError) as e:
        print(f"⚠️  Error fixing message {msg_id}: {e}")

# Fix parts table
cursor.execute("SELECT id, data FROM part WHERE session_id = ?", (session_id,))
fixed_part_count = 0

for part_id, part_data in cursor.fetchall():
    try:
        data = json.loads(part_data)
        if data.get('type') == 'text' and data.get('text', '') == '':
            data['text'] = '.'
            cursor.execute("UPDATE part SET data = ? WHERE id = ?",
                         (json.dumps(data), part_id))
            fixed_part_count += 1
    except (json.JSONDecodeError, KeyError, AttributeError) as e:
        print(f"⚠️  Error fixing part {part_id}: {e}")

conn.commit()
return fixed_msg_count, fixed_part_count

Run with: python3 fix-opencode-sessions.py [session_id]

```

Environment

  • OpenCode version: v1.3.x (dev branch, latest)
  • Provider: AWS Bedrock via LiteLLM proxy
  • SDK: @ai-sdk/openai-compatible
  • Model: anthropic.claude-sonnet-4-5-20250929-v1:0
  • OS: Linux (Ubuntu)

Impact

This affects any user connecting to Bedrock-backed models through:

  • LiteLLM proxy with openai-compatible
  • Databricks-hosted Claude models
  • Custom Bedrock proxies
  • Any provider that forwards to Bedrock without @ai-sdk/amazon-bedrock

Sessions become permanently unusable once corruption occurs, requiring manual database repair.

Metadata

Metadata

Assignees

Labels

coreAnything pertaining to core functionality of the application (opencode server stuff)

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions