Skip to content

fix(history): tolerate non-array message content in blocksToText - #262

Open
dex0shubham wants to merge 1 commit into
teamchong:mainfrom
dex0shubham:fix/blockstotext-nonarray
Open

fix(history): tolerate non-array message content in blocksToText#262
dex0shubham wants to merge 1 commit into
teamchong:mainfrom
dex0shubham:fix/blockstotext-nonarray

Conversation

@dex0shubham

@dex0shubham dex0shubham commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Problem

blocksToText (src/core/history.ts) handles the string case, then does:

for (const blk of content) {

assuming an array. An untrusted request body can carry a non-array content (e.g. {"role":"assistant","content":null}), and for...of null throws TypeError: content is not iterable.

The collapse path reaches this with raw, unvalidated content: messagesToHistorySegments calls blocksToText(m.content) for assistant turns (and via withoutTypedUserText, which returns null unchanged, for user turns), and req.messages is only checked with an outer Array.isArray — individual content fields are never validated. The throw propagates out of transformRequest and is turned into a 502 "pxpipe transform failed" at the proxy, so one malformed turn fails the whole request.

Every sibling in the module already guards exactly this shape and passes it through: findClosedPrefixBoundary, withoutTypedUserText, splitUserTyped, messageCacheControl (and the OpenAI-path twin chatContentToText). blocksToText was the lone exception.

Fix

Add the same !Array.isArray(content) guard, returning '' — a malformed turn serializes to empty and passes through instead of 502ing.

Added a unit test (blocksToText(null) / undefined), verified it throws on the old code and passes with the fix.

Full suite green (1207/1207); typecheck clean.

blocksToText handled the string case then fell into `for (const blk of content)`,
assuming an array. An untrusted body carrying `content: null` (or any non-array)
makes the for...of throw `TypeError: content is not iterable`. The collapse path
reaches it with raw, unvalidated content (messagesToHistorySegments -> blocksToText
on assistant turns, and via withoutTypedUserText which returns null unchanged for
user turns), so a single `{"role":"assistant","content":null}` turn 502s the whole
request ("pxpipe transform failed").

Every sibling serializer/scanner in this module already guards this
(findClosedPrefixBoundary, withoutTypedUserText, splitUserTyped, messageCacheControl
all `if (!Array.isArray(...))`). Add the same guard so a malformed turn serializes
to empty and passes through.
@dex0shubham
dex0shubham force-pushed the fix/blockstotext-nonarray branch from 4078861 to 1a91500 Compare September 2, 2026 07:14
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