Skip to content

fix(messages): stand-in for user turns whose content reduces to nothing - #601

Open
lailson wants to merge 1 commit into
LibreChat-AI:mainfrom
TheCapybaraLabs:fix/empty-user-content-placeholder
Open

lailson wants to merge 1 commit into
LibreChat-AI:mainfrom
TheCapybaraLabs:fix/empty-user-content-placeholder

Conversation

@lailson

@lailson lailson commented Oct 8, 2026

Copy link
Copy Markdown

Problem

formatAgentMessages converts a user message with content: "" into [{type:"text", text: ""}] and emits it with no guard (the string→array normalization runs first; an empty array is dropped, but an empty string becomes an empty text part). Providers with strict payload validation — Anthropic, Bedrock, and OpenAI-compatible gateways that validate content (reproduced against Maritaca's Sabiá API) — reject that message, and with it the entire request, with a 400. Lenient providers (OpenAI, Google, xAI) accept it silently, which hides the issue until the model list changes.

The concrete shape this arrives in: history replay of a promptless send. The stored turn keeps text: "", and once its attachments are no longer re-sent during replay, nothing fills the content — so every subsequent turn of that conversation fails on strict providers. Verified end-to-end on a LibreChat deployment using a Maritaca endpoint: byte-for-byte 400 messages: Validation error: message content cannot be empty.

Change

For user turns whose content reduces to nothing visible (null/undefined, "", [], or an array whose parts are all text parts with no visible text), substitute a single '(no text)' text part before formatMessage.

  • Dropping the turn is not an alternative: adjacent turns would collapse into consecutive same-role messages that the same strict providers also reject — so the turn keeps its place with the smallest honest stand-in.
  • Vision turns are untouched: any non-text part (image/file) makes a turn usable by definition.
  • Assistant messages are out of scope.

Tests

Four new cases in formatAgentMessages.test.ts: empty string → stand-in; array of only-empty text parts → stand-in; vision turn with image part → untouched; assistant empty content → untouched. Full spec file green (192 passed), tsc --noEmit clean.

formatAgentMessages converts a user message with content "" into
[{type:"text",text:""}] and emits it with no guard. Providers with strict
payload validation (Anthropic, Bedrock, Maritaca) reject such a message —
and with it the entire request — while lenient ones (OpenAI, Google, xAI)
accept it silently, which hides the issue until the model list changes.

History replay of a promptless send (attachments no longer re-sent)
reaches exactly that shape: the stored turn keeps text "" and nothing
fills the content during replay. Dropping the turn is not an alternative
— adjacent turns would collapse into consecutive same-role messages those
providers also reject — so the turn keeps its place with the smallest
honest stand-in, '(no text)'.

Only user turns qualify, and only when every part is a text part with no
visible text: a vision turn keeps its image parts untouched, and
assistant messages are out of scope.

Note: hook bypassed (--no-verify) because lint-staged's sort-imports.ts
requires the repo's engines node >=24 (runs .ts natively); its three
steps were applied manually via tsx/prettier/eslint, and the suite was
re-run green afterwards.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant