Skip to content

fix(chat): reject null/non-object entries in messages[] (#12643) - #13755

Merged
diegosouzapw merged 5 commits into
release/v3.8.51from
fix/12643-null-non-object-message-entries
Sep 17, 2026
Merged

diegosouzapw merged 5 commits into
release/v3.8.51from
fix/12643-null-non-object-message-entries

Conversation

@diegosouzapw

Copy link
Copy Markdown
Collaborator

Closes #12643

Root cause (short)

handleChatImplementation in src/sse/handlers/chat.ts has a guard family for the messages
field (built up across #5110/#6402/#6407/#6412) that checks array-ness, non-emptiness, and
presence — but never the shape of the individual entries. The route's Zod schema is
intentionally wide (z.array(z.unknown()), per #6402), so messages: [null] or messages: [42]
sailed straight through the guard chain into translation. Two confirmed unguarded crash sites:

  • open-sse/translator/request/openai-to-claude.ts — reads .role directly off each entry.
  • open-sse/services/sessionManager.ts (extractSystemPrompt) — same, reached from
    generateSessionId earlier in the dispatch path than the translator.
  • open-sse/services/contextManager.ts (fixToolPairs) — same unguarded shape assumption.

Any of these turns a malformed client request into an unhandled TypeError, surfacing as a raw
HTTP 500 instead of a clean 400.

Fix

Adds one more entry-shape guard clause to the existing chokepoint in chat.ts, right after the
empty-array check and before the missing-messages check: rejects the request with a 400
("messages: Expected array of objects") when any entry is null, a non-object primitive, or an
array (arrays are typeof "object" but not valid message objects).

Regression test

tests/unit/chat-messages-entry-objects-12643.test.ts (built on the existing
tests/integration/_chatPipelineHarness.ts harness).

RED (on unfixed release/v3.8.51 tip):

✖ #12643: messages: [null] is rejected with a clear 400
  TypeError: Cannot read properties of null (reading 'role')
      at sessionManager.ts:362:45 (extractSystemPrompt)
✖ #12643: messages with string/number entries are rejected with a clear 400
  AssertionError: must be a 400: [{"role":"user","content":"hi"},"oops"]
  502 !== 400
ℹ pass 1
ℹ fail 2

GREEN (with the fix):

✔ #12643: messages: [null] is rejected with a clear 400
✔ #12643: messages with string/number entries are rejected with a clear 400
✔ #12643: well-formed messages pass the entry guard
ℹ tests 3
ℹ pass 3
ℹ fail 0

Gates run

Existing tests aligned

None — no existing assertion encoded the old buggy contract. Ran the sibling guard suites for
regression: tests/unit/chat-messages-validation-6402.test.ts + tests/unit/chat-non-string-model-6407.test.ts
— 22/22 pass, no regression.

Credit

PR #12644 (@soroush5) proposed this exact fix — same guard shape, same error message — but was
closed on 2026-09-12 without merging, so the bug never actually shipped. This PR re-implements it
fresh against the current tip. Originally-proposed-by / Co-authored-by trailers added on the
commit.

diegosouzapw and others added 5 commits September 15, 2026 13:39
A messages array containing null (or any non-object entry, e.g. [null] or
[42]) passed every existing entry guard in chat.ts (#5110/#6402/#6407/#6412)
and reached downstream translators/session helpers that read `.role` /
`.content` directly off each entry (openai-to-claude.ts, sessionManager.ts,
contextManager.ts's fixToolPairs), crashing with a raw TypeError and
surfacing as an HTTP 500 instead of a clean 400. The route's Zod schema is
intentionally wide (z.array(z.unknown())), so this shape check belongs in
the handler's guard chain. Adds one more entry-shape guard clause to the
same chokepoint, rejecting the request with a clear 400 before any routing
or upstream call.

Regression test: tests/unit/chat-messages-entry-objects-12643.test.ts

PR #12644 (@soroush5) proposed this exact fix but was closed without
merging on 2026-09-12; this re-implements it fresh against the current tip
using the same guard shape and error message.

Originally-proposed-by: @soroush5 in #12644
Co-authored-by: soroush5 <mrsoroushahmadi@gmail.com>
…null-non-object-message-entries

# Conflicts:
#	config/quality/file-size-baseline.json
…null-non-object-message-entries

# Conflicts:
#	config/quality/file-size-baseline.json
# Conflicts:
#	config/quality/file-size-baseline.json
This branch rebaselined src/sse/handlers/chat.ts against an older tip. After
merging the current release tip the combined file is 2520 lines, so the 2500
ceiling no longer covers it.

The tip alone is already at 2509 — above the 2500 this PR had frozen — so most
of the gap is inherited, not introduced here. This PR's own contribution is the
+10 of the messages-entry guard itself. Ceiling refixed at the value the gate
reports for the merged tree.
@diegosouzapw
diegosouzapw merged commit 872376b into release/v3.8.51 Sep 17, 2026
7 of 11 checks passed
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…rence#12643) (cheaper-inference#13755)

* fix(chat): reject null/non-object entries in messages[] (cheaper-inference#12643)

A messages array containing null (or any non-object entry, e.g. [null] or
[42]) passed every existing entry guard in chat.ts (cheaper-inference#5110/cheaper-inference#6402/cheaper-inference#6407/cheaper-inference#6412)
and reached downstream translators/session helpers that read `.role` /
`.content` directly off each entry (openai-to-claude.ts, sessionManager.ts,
contextManager.ts's fixToolPairs), crashing with a raw TypeError and
surfacing as an HTTP 500 instead of a clean 400. The route's Zod schema is
intentionally wide (z.array(z.unknown())), so this shape check belongs in
the handler's guard chain. Adds one more entry-shape guard clause to the
same chokepoint, rejecting the request with a clear 400 before any routing
or upstream call.

Regression test: tests/unit/chat-messages-entry-objects-12643.test.ts

PR cheaper-inference#12644 (@soroush5) proposed this exact fix but was closed without
merging on 2026-09-12; this re-implements it fresh against the current tip
using the same guard shape and error message.

Originally-proposed-by: @soroush5 in cheaper-inference#12644
Co-authored-by: soroush5 <mrsoroushahmadi@gmail.com>

* chore(quality): refix the chat.ts ceiling for the merged tree

This branch rebaselined src/sse/handlers/chat.ts against an older tip. After
merging the current release tip the combined file is 2520 lines, so the 2500
ceiling no longer covers it.

The tip alone is already at 2509 — above the 2500 this PR had frozen — so most
of the gap is inherited, not introduced here. This PR's own contribution is the
+10 of the messages-entry guard itself. Ceiling refixed at the value the gate
reports for the merged tree.

---------

Co-authored-by: soroush5 <mrsoroushahmadi@gmail.com>
@diegosouzapw
diegosouzapw deleted the fix/12643-null-non-object-message-entries branch October 18, 2026 23:48
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.

fix(api): Null/non-object entries in messages[] pass guards and crash translators with HTTP 500

1 participant