Skip to content

fix(anthropic): drop whitespace-only text blocks reaching the Messages API - #77509

Merged
kshitijk4poor merged 2 commits into
NousResearch:mainfrom
kshitijk4poor:review/77134-anthropic-blank-text
Aug 3, 2026
Merged

fix(anthropic): drop whitespace-only text blocks reaching the Messages API#77509
kshitijk4poor merged 2 commits into
NousResearch:mainfrom
kshitijk4poor:review/77134-anthropic-blank-text

Conversation

@kshitijk4poor

@kshitijk4poor kshitijk4poor commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes two bugs in convert_messages_to_anthropic() that each cause Anthropic HTTP 400 "text content blocks must contain non-whitespace text", permanently wedging sessions after context compaction or when tool results contain blank text blocks.

Root cause

Two independent bugs in the final conversion step before every Anthropic messages.create() call:

  1. _ensure_leading_user_turn() synthesized a filler user turn with text: " " (whitespace-only) — the guard traded a "leading assistant turn" 400 for the "text content blocks" 400.

  2. _convert_user_message() filtered blank text blocks with an all-or-nothing all() check — vacuously True when there are zero text-type blocks (nuking valid images/documents), and False as soon as any single text block is non-blank (leaving sibling blank text blocks untouched on the wire).

Changes

  • agent/anthropic_adapter.py: _ensure_leading_user_turn() uses _EMPTY_TEXT_PLACEHOLDER ("(empty)") instead of " ". _convert_user_message() does per-block filtering (dropping only blank text, preserving images/tool blocks). New _scrub_blank_text_blocks() final defense-in-depth pass runs last in convert_messages_to_anthropic(), also scrubbing nested tool_result content.
  • tests/agent/test_anthropic_adapter.py: 9 new end-to-end regression tests (TestFinalPayloadHasNoBlankTextBlocks) covering all blank-content shapes. One pre-existing test fixed to expect the non-blank placeholder.
  • contributors/emails/pooyan6@gmail.com: AUTHOR_MAP entry for contributor @pooyan6.

Follow-up fix (on top of contributor commit)

_convert_user_message hand-inlined the same blank-text-filter + cache_control-relocation + placeholder-fallback logic that _fix_blank_text_blocks_in_list (added in the same PR) implements as a reusable helper. Replaced the inline copy (~35 lines) with a call to the helper, eliminating the duplication.

Validation

Before After
test_anthropic_adapter.py 83 pass 92 pass (9 new)
E2E (real imports) 7/7 pass
Ruff clean clean

Closes #77134

Salvage of @pooyan6's work — cherry-picked with authorship preserved.

Infographic

blank-text-block-scrubbing

pooyan6 and others added 2 commits August 3, 2026 14:08
…s API

Root cause: two independent bugs in convert_messages_to_anthropic()
(agent/anthropic_adapter.py), the final conversion step before every
Anthropic messages.create() call, both producing HTTP 400 "text content
blocks must contain non-whitespace text":

1. _ensure_leading_user_turn() synthesized a filler user turn with
   content [{"type": "text", "text": " "}] (a single space) whenever the
   built payload didn't start with role=user (e.g. after context
   compaction leaves a leading assistant summary). The space is itself
   whitespace-only, so the guard traded a "leading assistant turn" 400
   for the "text content blocks" 400 it now hits. Fixed to reuse the
   existing non-blank _EMPTY_TEXT_PLACEHOLDER ("(empty)").

2. _convert_user_message() filtered blank text blocks from list-type
   user content with an all-or-nothing check:
   all(blank for b in blocks if b.type == "text"). This is vacuously
   true when a message has zero text-type blocks (silently destroying
   valid non-text blocks like images/documents it never inspected), and
   false as soon as any single text block is non-blank — which let a
   *sibling* blank text block sit untouched next to valid content and
   reach Anthropic as-is. Replaced with per-block filtering (mirroring
   the assistant-side logic already in _convert_assistant_message),
   preserving all non-blank/non-text blocks and relocating any
   cache_control marker carried by a dropped block.

Also added _scrub_blank_text_blocks(), a final defense-in-depth pass run
as the last step of convert_messages_to_anthropic() (after every other
transform, including nested tool_result content lists) so a blank text
block from any current or future producer never reaches the wire. It
logs only structural metadata (message index, role, content location,
block index/type) — never message text, tool arguments, tokens, or
credentials.

An earlier local patch to sanitize_api_messages() (agent_runtime_
helpers.py) attempted to fix this by rewriting blank assistant content
before the OpenAI->Anthropic conversion step, but the real leaks were
introduced downstream of that sanitizer, inside the Anthropic-specific
converter itself — the patch never touched the actual defect and has
been fully reverted (agent_runtime_helpers.py is back to its committed
state; verified via `git diff` showing no changes).

Verified against a real Telegram message end-to-end: the gateway no
longer produces the "text content blocks must contain non-whitespace
text" error on a fresh conversation turn.

Testing:
- 9 new end-to-end regression tests in test_anthropic_adapter.py
  (TestFinalPayloadHasNoBlankTextBlocks) covering content="",
  content="   ", content=[{"type":"text","text":""}], mixed blank+valid
  text, blank text next to a valid tool block, an assistant tool-call
  message with blank content, the leading-synthesized-user-turn case,
  and a blank text block nested inside a tool_result's own content list.
- Fixed one pre-existing test that had asserted the broken " " filler
  behavior as correct.
- Full tests/agent/ + tests/run_agent/ suite (4671 tests) run against
  both the patched tree and a stashed pre-fix baseline: identical 148
  pre-existing failures in both runs (unrelated subsystems — codex
  app-server integration, credential-pool interrupt handling, OpenAI
  client lifecycle), zero failures unique to either side.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…in_list

_convert_user_message hand-inlined the same blank-text-filter +
cache_control-relocation + placeholder-fallback logic that
_fix_blank_text_blocks_in_list (added in the cherry-picked commit)
implements as a reusable helper. Replace the inline copy with a call
to the helper, eliminating ~35 lines of duplication.

Follow-up fix on top of PR NousResearch#77134 by @pooyan6.
@kshitijk4poor
kshitijk4poor enabled auto-merge (rebase) August 3, 2026 08:44
@kshitijk4poor
kshitijk4poor merged commit 633bd35 into NousResearch:main Aug 3, 2026
35 checks passed
@alt-glitch alt-glitch added type/bug Something isn't working P0 Critical — data loss, security, crash loop comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/anthropic Anthropic native Messages API sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) labels Aug 3, 2026
@kshitijk4poor
kshitijk4poor deleted the review/77134-anthropic-blank-text branch August 5, 2026 07:09
teknium1 pushed a commit that referenced this pull request Aug 15, 2026
Residual from PR #70910 after #77509 landed the message-list scrub: a
whitespace-only system content block carrying a cache_control marker
still reached the wire and 400'd the whole request ("text content
blocks must contain non-whitespace text"), wedging the session on every
retry. The block cannot be dropped (it carries the cache breakpoint),
so coerce its text to the shared non-whitespace placeholder when
extracting the system param, copying the block so caller message dicts
are never mutated.

Adds SHL0MS's request-level regression suite from #70910; four of its
five cases already pass on main via #77509 — the system-block case
fails without this fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P0 Critical — data loss, security, crash loop provider/anthropic Anthropic native Messages API sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants