bytedance/deer-flow · error
Thread history returned an invalid response.
Error message
Thread history returned an invalid response.
What it means
Thrown by parseThreadMessagesPageResponse when the JSON from the thread-history endpoint does not match the ThreadMessagesPageResponse contract (object with data: unknown[], has_more: boolean, next_before_seq). The guard exists because the static RunMessage type cannot protect this boundary from version skew or malformed responses; per-row seq validation continues after these shape checks.
Source
Thrown at frontend/src/core/threads/hooks.ts:338
data: RunMessage[];
has_more: boolean;
next_before_seq: number | null;
};
function isValidThreadMessageSeq(value: unknown): value is number {
return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
}
/**
* Validate the sequence fields that history reconciliation and pagination use
* as runtime identities. The static RunMessage type cannot protect this JSON
* boundary from version skew or malformed responses.
*/
export function parseThreadMessagesPageResponse(
value: unknown,
): ThreadMessagesPageResponse {
if (typeof value !== "object" || value === null) {
throw new Error("Thread history returned an invalid response.");
}
const data = Reflect.get(value, "data");
const hasMore = Reflect.get(value, "has_more");
const nextBeforeSeq = Reflect.get(value, "next_before_seq");
if (!Array.isArray(data) || typeof hasMore !== "boolean") {
throw new Error("Thread history returned an invalid response.");
}
const seenSeqs = new Set<number>();
for (const row of data) {
const seq =
typeof row === "object" && row !== null
? Reflect.get(row, "seq")
: undefined;
if (!isValidThreadMessageSeq(seq)) {
throw new Error("Thread history returned a row with an invalid seq.");
}View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Log the raw value at the boundary to see which field failed (value, data, has_more, or row seq)
- Align frontend and backend versions (this envelope is version-coupled by design)
- If a proxy wraps responses, configure it to pass /api/threads JSON through unmodified
- On parse failure, retry the page request once and then surface a 'history unavailable' state rather than a crash
Example fix
// before
const page = parseThreadMessagesPageResponse(await res.json());
// after
let page: ThreadMessagesPageResponse;
try {
page = parseThreadMessagesPageResponse(await res.json());
} catch {
reportSchemaMismatch(threadId);
return emptyPage(); // graceful degradation for the history panel
} Defensive patterns
Strategy: type-guard
Validate before calling
function looksLikeMessagesPage(v: unknown): boolean {
if (typeof v !== 'object' || v === null) return false;
return Array.isArray(Reflect.get(v, 'data')) && typeof Reflect.get(v, 'has_more') === 'boolean';
} Type guard
export function isThreadMessagesPage(v: unknown): v is ThreadMessagesPageResponse {
if (typeof v !== 'object' || v === null) return false;
const data = Reflect.get(v, 'data');
const hasMore = Reflect.get(v, 'has_more');
const nextBeforeSeq = Reflect.get(v, 'next_before_seq');
return (
Array.isArray(data) &&
typeof hasMore === 'boolean' &&
(nextBeforeSeq === null || nextBeforeSeq === undefined || (typeof nextBeforeSeq === 'number' && Number.isSafeInteger(nextBeforeSeq)))
);
} Try / catch
let raw: unknown = await res.json();
if (!isThreadMessagesPage(raw)) {
reportHistorySchemaMismatch(threadId, raw);
return emptyHistoryPage(); // degrade instead of crash
}
return parseThreadMessagesPageResponse(raw); Prevention
- Pre-check the envelope with a type guard before the throwing parser
- Pin frontend and backend versions together on deploy
- Log the raw payload on mismatch to catch proxy wrappers early
When it happens
Trigger: Backend older/newer than the frontend expecting different history envelope fields; a proxy returning an HTML error page parsed as JSON object; has_more missing or data null; per-row seq missing, non-safe-integer, duplicated or < 1 in later checks.
Common situations: Frontend deployed ahead of backend after an upgrade; API gateway injecting wrapper objects; backend returning {error:...} with 200 during partial failures.
Related errors
- Failed to update MCP configuration
- Failed to create side conversation.
- Failed to load thread token usage.
- Failed to branch conversation.
- Failed to update conversation.
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/8397a2c261c276e3.
Report an issue: GitHub.