openclaw - 💡(How to fix) Fix [Bug]: pi-ai anthropic adapter does not merge consecutive same-role messages — causes 400 errors with custom proxies [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#72889Fetched 2026-04-28 06:30:49
View on GitHub
Comments
0
Participants
1
Timeline
2
Reactions
3
Participants
Timeline (top)
cross-referenced ×1subscribed ×1

The convertMessages() function in @mariozechner/pi-ai/dist/providers/anthropic.js does not merge consecutive messages with the same role before sending to the Anthropic API. The Anthropic Messages API requires strict user/assistant alternation — violating this causes 400 Bad Request.

This is especially triggered when using custom Anthropic-compatible proxies (e.g., corporate gateways, ccproxy) that enforce alternation more strictly than the native Anthropic endpoint. But even without proxies, upstream message transforms (tool result batching, system event injection, aborted responses) can produce back-to-back messages with the same role.

Error Message

  • #17124 (MiniMax consecutive same-role 400 error — closed)
  • #58567 (trailing assistant message causes prefill error loop)
  • #72865 (4.25 regression: Bedrock prefill error)

Root Cause

In @mariozechner/pi-ai/dist/providers/anthropic.js, the convertMessages() function (around line 880, before return params) does not check for or merge consecutive same-role messages. The OpenClaw wrapper layer (provider-stream-*.js) has its own convertAnthropicMessages() but it also lacks this normalization.

Fix Action

Workaround

We apply a local patch that merges consecutive same-role content blocks and strips trailing assistant messages:

// After message conversion, before return params:
for (let i = params.length - 1; i > 0; i -= 1) {
    const cur = params[i], prev = params[i - 1];
    if (cur.role !== prev.role) continue;
    const toBlocks = (c) => typeof c === "string" ? [{ type: "text", text: c }] : Array.isArray(c) ? c : [];
    prev.content = [...toBlocks(prev.content), ...toBlocks(cur.content)];
    params.splice(i, 1);
}
while (params.length > 0 && params[params.length - 1].role === "assistant") { params.pop(); }

Code Example

// After message conversion, before return params:
for (let i = params.length - 1; i > 0; i -= 1) {
    const cur = params[i], prev = params[i - 1];
    if (cur.role !== prev.role) continue;
    const toBlocks = (c) => typeof c === "string" ? [{ type: "text", text: c }] : Array.isArray(c) ? c : [];
    prev.content = [...toBlocks(prev.content), ...toBlocks(cur.content)];
    params.splice(i, 1);
}
while (params.length > 0 && params[params.length - 1].role === "assistant") { params.pop(); }
RAW_BUFFERClick to expand / collapse

Bug type

Missing message normalization in @mariozechner/pi-ai anthropic provider adapter.

Summary

The convertMessages() function in @mariozechner/pi-ai/dist/providers/anthropic.js does not merge consecutive messages with the same role before sending to the Anthropic API. The Anthropic Messages API requires strict user/assistant alternation — violating this causes 400 Bad Request.

This is especially triggered when using custom Anthropic-compatible proxies (e.g., corporate gateways, ccproxy) that enforce alternation more strictly than the native Anthropic endpoint. But even without proxies, upstream message transforms (tool result batching, system event injection, aborted responses) can produce back-to-back messages with the same role.

Reproduction

  1. Configure a custom provider with api: "anthropic-messages" pointing to an Anthropic-compatible proxy
  2. Have a session where upstream transforms produce consecutive user or assistant messages (e.g., multi-tool-result + system event, or aborted assistant turn followed by retry)
  3. Send a message
  4. Expected: Messages are normalized to strict alternation before API call
  5. Actual: Raw messages with consecutive same-role entries are sent, proxy returns 400

Root Cause

In @mariozechner/pi-ai/dist/providers/anthropic.js, the convertMessages() function (around line 880, before return params) does not check for or merge consecutive same-role messages. The OpenClaw wrapper layer (provider-stream-*.js) has its own convertAnthropicMessages() but it also lacks this normalization.

Workaround

We apply a local patch that merges consecutive same-role content blocks and strips trailing assistant messages:

// After message conversion, before return params:
for (let i = params.length - 1; i > 0; i -= 1) {
    const cur = params[i], prev = params[i - 1];
    if (cur.role !== prev.role) continue;
    const toBlocks = (c) => typeof c === "string" ? [{ type: "text", text: c }] : Array.isArray(c) ? c : [];
    prev.content = [...toBlocks(prev.content), ...toBlocks(cur.content)];
    params.splice(i, 1);
}
while (params.length > 0 && params[params.length - 1].role === "assistant") { params.pop(); }

Related Issues

  • #17728 (MiniMax consecutive same-role — closed, but only fixed for MiniMax provider)
  • #17124 (MiniMax consecutive same-role 400 error — closed)
  • #58567 (trailing assistant message causes prefill error loop)
  • #72865 (4.25 regression: Bedrock prefill error)

Environment

  • OpenClaw: 2026.4.25 (aa36ee6)
  • pi-ai: bundled version in openclaw 2026.4.25
  • Provider: custom anthropic-messages via corporate proxy
  • OS: macOS (Darwin 25.4.0)

extent analysis

TL;DR

Apply a patch to the convertMessages() function in @mariozechner/pi-ai/dist/providers/anthropic.js to merge consecutive same-role messages before sending to the Anthropic API.

Guidance

  • Identify the convertMessages() function in @mariozechner/pi-ai/dist/providers/anthropic.js and apply the provided patch to merge consecutive same-role messages.
  • Verify that the patch fixes the issue by testing with a session that produces consecutive user or assistant messages.
  • Consider updating the OpenClaw wrapper layer (provider-stream-*.js) to include the same normalization.
  • If using a custom Anthropic-compatible proxy, test the fix with the proxy to ensure it resolves the 400 Bad Request error.

Example

// After message conversion, before return params:
for (let i = params.length - 1; i > 0; i -= 1) {
    const cur = params[i], prev = params[i - 1];
    if (cur.role !== prev.role) continue;
    const toBlocks = (c) => typeof c === "string" ? [{ type: "text", text: c }] : Array.isArray(c) ? c : [];
    prev.content = [...toBlocks(prev.content), ...toBlocks(cur.content)];
    params.splice(i, 1);
}
while (params.length > 0 && params[params.length - 1].role === "assistant") { params.pop(); }

Notes

The provided patch is a workaround and may not be the final solution. It's recommended to update the @mariozechner/pi-ai package to a version that includes the fix, if available.

Recommendation

Apply the workaround patch to the convertMessages() function, as it directly addresses the issue and provides a temporary solution until a fixed version of @mariozechner/pi-ai is available.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING