bytedance/deer-flow · error

Thread history returned duplicate seq values.

Error message

Thread history returned duplicate seq values.

What it means

The thread history parser tracks every row's seq in a Set to guarantee ordering uniqueness within one page. If two rows in the same response carry the same seq, pagination cursors would become ambiguous, so the parser throws rather than render a duplicated history.

Source

Thrown at frontend/src/core/threads/hooks.ts:358

  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.");
    }
    if (seenSeqs.has(seq)) {
      throw new Error("Thread history returned duplicate seq values.");
    }
    seenSeqs.add(seq);
  }

  if (
    (hasMore && !isValidThreadMessageSeq(nextBeforeSeq)) ||
    (!hasMore && nextBeforeSeq !== null)
  ) {
    throw new Error(
      "Thread history returned an invalid next_before_seq cursor.",
    );
  }

  return value as ThreadMessagesPageResponse;
}

export function getThreadHistoryNextPageParam(
  lastPage: ThreadMessagesPageResponse,

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. Reproduce the failing page (threadId + before cursor) via curl and confirm which seq repeats.
  2. Inspect the backend history query for fan-out (add DISTINCT on the message primary key, or aggregate joined child rows into arrays).
  3. If a data migration produced duplicate seq values, run a repair migration that reassigns unique monotonically increasing seqs per thread.
  4. Add a backend test asserting unique, strictly ordered seqs per page.

Example fix

-- before: join fan-out duplicates message rows
SELECT m.* FROM messages m JOIN attachments a ON a.message_id = m.id WHERE m.thread_id = :tid;

-- after: dedupe on the message primary key
SELECT DISTINCT ON (m.id) m.* FROM messages m LEFT JOIN attachments a ON a.message_id = m.id WHERE m.thread_id = :tid ORDER BY m.id, m.seq;
Defensive patterns

Strategy: validation

Validate before calling

const seqs = rows.map((r) => r.seq);
if (new Set(seqs).size !== seqs.length) { /* duplicate seq in page: log and request the page again / fall back to full reload */ }

Type guard

function hasUniqueSeqs(rows: Array<{ seq: number }>): boolean {
  return new Set(rows.map((r) => r.seq)).size === rows.length;
}

Try / catch

catch (e) { if (e.message === "Thread history returned duplicate seq values.") { await queryClient.invalidateQueries({ queryKey: threadHistoryKey }); return; } throw e; }

Prevention

When it happens

Trigger: The backend returns the same message row twice in one page — typically a JOIN fan-out in the SQL query (e.g. joining messages to attachments or roles produces one row per pair), or a UNION of overlapping ranges, or a retry/merge bug in the query builder.

Common situations: A new message-to-attachment 1:N join added to the history query without DISTINCT or proper aggregation; migration that backfills seq values with collisions; two shards merging pages with overlapping seq ranges.

Related errors


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