Skip to content

Bedrock replay can enter empty assistant content death loop #71572

Description

@sesame437

Summary

OpenClaw can persist assistant messages with empty content: [] after stream/provider errors. With AWS Bedrock Converse, any later replay containing that assistant turn fails with:

Validation error: The content field in the Message object at messages.N is empty. Add a ContentBlock object to the content field and try again.

Once this happens, the session can enter a death loop: every new inbound message replays the same invalid assistant turn, Bedrock rejects the request, and another failed/error assistant turn may be persisted.

Environment

  • OpenClaw: 2026.4.23
  • Provider/API: AWS Bedrock Converse / Converse stream
  • Observed with embedded agent sessions and Feishu channel delivery

Observed symptoms

  • A session stops responding after one invalid assistant turn is written.
  • Gateway restart does not help because the invalid JSONL entry is persisted.
  • New user messages keep failing at Bedrock validation before the model can answer.
  • In some sessions, transcript-only assistant entries such as OpenClaw delivery mirror/injected messages can also be replayed to the provider.

Root cause pattern

There are three related transcript hygiene gaps:

  1. Stream/provider error assistant messages can be built with content: [].
  2. Replay normalization currently handles string assistant content, but does not defensively normalize/drop empty or invalid assistant content arrays before provider replay.
  3. Session file repair repairs malformed JSONL lines, but does not repair semantically invalid assistant messages that are valid JSON yet invalid for provider replay.

For Bedrock, an assistant message must contain at least one content block. Empty assistant arrays are therefore a hard replay poison.

Suggested fix

A robust fix would likely include:

  • Ensure stream error assistant messages always contain a non-empty text block, e.g. a short sentinel such as [assistant turn failed before producing content].
  • In replay history normalization, normalize assistant content arrays by dropping invalid/empty text blocks and replacing an empty result with the same sentinel text block.
  • Filter transcript-only OpenClaw assistant messages from provider replay when provider === "openclaw" and model is delivery-mirror or gateway-injected.
  • Extend session file repair to rewrite semantically invalid assistant messages and optionally drop transcript-only assistant messages, not just malformed JSONL lines.
  • Make the repair idempotent so a healthy session is not rewritten repeatedly.

Regression tests that would cover this

  • buildStreamErrorAssistantMessage never returns content: [].
  • normalizeAssistantReplayContent converts assistant content: [] to a non-empty text content block.
  • normalizeAssistantReplayContent filters OpenClaw delivery-mirror / gateway-injected assistant messages before provider replay.
  • repairSessionFileIfNeeded repairs persisted assistant content: [] and is idempotent on a second run.

Local validation

A local hotfix with the behavior above was applied to a 2026.4.23 installation and tested against previously poisoned sessions. After repair:

  • Main session: tool calls/results remained paired.
  • Subagent long task: completed with web fetch, file reads, HTML generation, screenshot, and Feishu image delivery.
  • No synthetic error result for transcript repair tool results appeared.
  • No empty assistant content remained.
  • No new content field ... is empty Bedrock validation error occurred.

I am filing this as an issue rather than a PR because the local validation was done against packaged dist output; the upstream fix should be implemented in the TypeScript sources and covered by the tests above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions