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

  1. Log the raw value at the boundary to see which field failed (value, data, has_more, or row seq)
  2. Align frontend and backend versions (this envelope is version-coupled by design)
  3. If a proxy wraps responses, configure it to pass /api/threads JSON through unmodified
  4. 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

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


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/8397a2c261c276e3. Report an issue: GitHub.