Skip to content

fix: prevent empty assistant content from crashing provider APIs#238

Closed
wujiaming88 wants to merge 1 commit into
Martian-Engineering:mainfrom
wujiaming88:fix/empty-assistant-content-crash
Closed

fix: prevent empty assistant content from crashing provider APIs#238
wujiaming88 wants to merge 1 commit into
Martian-Engineering:mainfrom
wujiaming88:fix/empty-assistant-content-crash

Conversation

@wujiaming88

Copy link
Copy Markdown
Contributor

Problem

When LCM assembles context from the SQLite database, assistant messages can end up with an empty content array ([]). This causes provider APIs like Anthropic Claude to reject the request with:

The content field in the Message object at messages.0 is empty. Add a ContentBlock object to the content field and try again.

Root Cause

In src/assembler.ts, contentFromParts() returns [] when:

  1. The message has no stored parts (parts.length === 0)
  2. The fallback content is an empty string ("" is falsy)
// Before (bug):
return fallbackContent ? [{ type: "text", text: fallbackContent }] : [];

This can happen when:

  • A stored assistant message has no message_parts rows and empty content text
  • Tool-call-only assistant turns are stored without text content
  • Session reset/compaction leaves orphaned empty assistant records

Additionally, filterNonFreshAssistantToolCalls() can strip all tool-call blocks from a non-fresh assistant message, leaving content: [], but the empty-check only skips messages where the filtering itself produced an empty array — messages that started empty pass through.

Fix

Two defensive layers:

  1. contentFromParts(): Always return at least one text block for assistant messages instead of an empty array:

    return [{ type: "text", text: fallbackContent || "" }];
  2. assemble() safety net: Filter out any assistant messages that still have empty content arrays before returning, catching edge cases from tool-call orphan filtering:

    const nonEmptyMessages = rawMessages.filter((msg) => {
      if (msg?.role === "assistant" && Array.isArray(msg.content) && msg.content.length === 0) {
        return false;
      }
      return true;
    });

Testing

  • Verified the fix handles the exact error scenario from production logs
  • Both Anthropic and OpenAI APIs require non-empty content arrays for assistant messages
  • The changeset is included for version bumping

contentFromParts() returned [] when both message parts and fallback
content were empty for assistant messages. Providers like Anthropic
reject messages with zero ContentBlock entries.

Two fixes:
1. contentFromParts() now always returns at least one text block for
   assistant role instead of an empty array.
2. assemble() filters out assistant messages with empty content arrays
   as a safety net after tool-call orphan filtering.
@wujiaming88 wujiaming88 closed this Apr 3, 2026
wujiaming88 pushed a commit to wujiaming88/lossless-claw that referenced this pull request Apr 3, 2026
When tool-use-only assistant turns are stored with content='' and zero
message_parts, or when filterNonFreshAssistantToolCalls strips all
tool_use blocks from a non-fresh assistant message, the resulting
content array is empty ([]) or the content string is falsy.

Anthropic (and other providers) reject messages with empty content:
  'The content field in the Message object at messages.0 is empty'

Add an explicit filter in assemble() to remove these empty assistant
messages before passing to sanitizeToolUseResultPairing and the API.
The filter only targets assistant messages — user messages with empty
content are left untouched (provider may handle differently).

Closes Martian-Engineering#238
jalehman pushed a commit that referenced this pull request Apr 3, 2026
When tool-use-only assistant turns are stored with content='' and zero
message_parts, or when filterNonFreshAssistantToolCalls strips all
tool_use blocks from a non-fresh assistant message, the resulting
content array is empty ([]) or the content string is falsy.

Anthropic (and other providers) reject messages with empty content:
  'The content field in the Message object at messages.0 is empty'

Add an explicit filter in assemble() to remove these empty assistant
messages before passing to sanitizeToolUseResultPairing and the API.
The filter only targets assistant messages — user messages with empty
content are left untouched (provider may handle differently).

Closes #238

Co-authored-by: wujiaming88 <wujiaming88@example.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant