fix: validate empty user messages to prevent Anthropic API 400 errors (#3322)

When user messages have empty content (e.g., Discord @mention-only
messages, unrecognized attachments), the Anthropic API rejects the
request with 'user messages must have non-empty content'.

Changes:
- anthropic_adapter.py: Add empty content validation for user messages
  (string and list formats), matching the existing pattern for assistant
  and tool messages. Empty content gets '(empty message)' placeholder.

- discord.py: Defense-in-depth check at gateway layer to catch empty
  messages before they enter session history.

- Add 4 regression tests covering empty string, whitespace-only,
  empty list, and empty text block scenarios.

Fixes #3143

Co-authored-by: Bartok9 <bartok9@users.noreply.github.com>
This commit is contained in:
Teknium
2026-03-26 19:24:03 -07:00
committed by GitHub
parent 03396627a6
commit 3f95e741a7
3 changed files with 59 additions and 5 deletions

View File

@@ -801,6 +801,48 @@ class TestConvertMessages:
assert all(not (b.get("type") == "text" and b.get("text") == "") for b in assistant_blocks)
assert any(b.get("type") == "tool_use" for b in assistant_blocks)
def test_empty_user_message_string_gets_placeholder(self):
"""Empty user message strings should get '(empty message)' placeholder.
Anthropic rejects requests with empty user message content.
Regression test for #3143 — Discord @mention-only messages.
"""
messages = [
{"role": "user", "content": ""},
]
_, result = convert_messages_to_anthropic(messages)
assert result[0]["role"] == "user"
assert result[0]["content"] == "(empty message)"
def test_whitespace_only_user_message_gets_placeholder(self):
"""Whitespace-only user messages should also get placeholder."""
messages = [
{"role": "user", "content": " \n\t "},
]
_, result = convert_messages_to_anthropic(messages)
assert result[0]["content"] == "(empty message)"
def test_empty_user_message_list_gets_placeholder(self):
"""Empty content list for user messages should get placeholder block."""
messages = [
{"role": "user", "content": []},
]
_, result = convert_messages_to_anthropic(messages)
assert result[0]["role"] == "user"
assert isinstance(result[0]["content"], list)
assert len(result[0]["content"]) == 1
assert result[0]["content"][0] == {"type": "text", "text": "(empty message)"}
def test_user_message_with_empty_text_blocks_gets_placeholder(self):
"""User message with only empty text blocks should get placeholder."""
messages = [
{"role": "user", "content": [{"type": "text", "text": ""}, {"type": "text", "text": " "}]},
]
_, result = convert_messages_to_anthropic(messages)
assert result[0]["role"] == "user"
assert isinstance(result[0]["content"], list)
assert result[0]["content"] == [{"type": "text", "text": "(empty message)"}]
# ---------------------------------------------------------------------------
# Build kwargs