Skip to content

fix(anthropic): non-whitespace placeholder for synthetic leading user turn - #70159

Open
songxujiangyang wants to merge 1 commit into
NousResearch:mainfrom
songxujiangyang:fix/whitespace-placeholder-leading-user-turn
Open

fix(anthropic): non-whitespace placeholder for synthetic leading user turn#70159
songxujiangyang wants to merge 1 commit into
NousResearch:mainfrom
songxujiangyang:fix/whitespace-placeholder-leading-user-turn

Conversation

@songxujiangyang

Copy link
Copy Markdown

Summary

_ensure_leading_user_turn (in agent/anthropic_adapter.py) prepends a synthetic user message when messages[0] is not role=user. This happens after a second context compaction on the auto path, where the summary is emitted as role=assistant with nothing in front of it (the system prompt lives outside messages[]), so messages[0] ends up assistant and the Messages API would 400.

The synthetic placeholder used a single space " " as its text.

The bug

Anthropic's own API tolerates a whitespace-only text block, but stricter Anthropic-compatible endpoints reject it. Observed against zenmux.ai (/api/anthropic):

HTTP 400 invalid_params: messages: text content blocks must contain non-whitespace text

This error is non-retryable, so once a conversation has compacted twice, every subsequent turn fails permanently. In our case a Feishu DM session silently stopped replying after auto-compaction — each inbound message hit the 400 and never recovered.

Fix

Use a minimal non-blank placeholder "." instead of " ". One-line change in the shared helper, so every caller (all provider paths that route through convert_messages_to_anthropic) is fixed at once.

Tests

The two existing tests froze the exact placeholder byte (== [{"type": "text", "text": " "}]) — change-detector shape. Updated them to assert the behavior contract instead: the prepended leading turn is role=user with non-whitespace text. This keeps them meaningful regardless of the exact placeholder character.

tests/agent/test_anthropic_adapter.py … 179 passed

(3 unrelated TestRunOauthSetupToken failures are pre-existing on main, verified by stashing this change.)

… turn

_ensure_leading_user_turn prepends a synthetic user message when the
first message is assistant (happens after a second context compaction on
the auto path, where the summary is emitted as role=assistant). The
placeholder text was a single space " ".

Anthropic's own API tolerates whitespace-only text blocks, but stricter
Anthropic-compatible endpoints (e.g. zenmux.ai) reject them with

  HTTP 400 invalid_params: messages: text content blocks must contain
  non-whitespace text

which is non-retryable and bricks every subsequent turn in any
conversation that has compacted twice. Use a minimal non-blank
placeholder "." instead.

Tests updated from freezing the exact placeholder byte to asserting the
behavior contract (leading turn is role=user with non-whitespace text).
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/anthropic Anthropic native Messages API sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state duplicate This issue or pull request already exists labels Jul 23, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #70081: both patches replace the whitespace-only synthesized Anthropic leading user turn with a printable placeholder to prevent the same strict-endpoint HTTP 400.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for identifying the assembly-time gap. The premise is confirmed on current main: agent/anthropic_adapter.py:2612-2613 still synthesizes a leading user block containing a single space, after the per-message conversion and filtering chain (agent/anthropic_adapter.py:2672-2674).

Suggested changes

  • During salvage, use the existing _EMPTY_TEXT_PLACEHOLDER rather than a new "." literal. Current main defines that canonical non-whitespace value at agent/anthropic_adapter.py:1931 and uses it in the existing whitespace-safety path at agent/anthropic_adapter.py:1950.
  • Update the current leading-assistant regression at tests/agent/test_anthropic_adapter.py:871-884 to assert non-whitespace behavior; it still freezes the single-space value.

This is an automated hermes-sweeper review.

"""
if result and result[0].get("role") != "user":
result.insert(0, {"role": "user", "content": [{"type": "text", "text": " "}]})
# ponytail: some Anthropic-compatible endpoints (e.g. zenmux) reject a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current main already has _EMPTY_TEXT_PLACEHOLDER for this exact non-whitespace invariant. Please use that canonical constant here rather than introducing a second placeholder literal.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 30, 2026
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 duplicate This issue or pull request already exists P2 Medium — degraded but workaround exists provider/anthropic Anthropic native Messages API sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants