{"record":{"id":"602fb7d9d96dcf49","repo":"bytedance/deer-flow","slug":"thread-history-returned-duplicate-seq-values","errorCode":null,"errorMessage":"Thread history returned duplicate seq values.","messagePattern":"Thread history returned duplicate seq values\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/core/threads/hooks.ts","lineNumber":358,"sourceCode":"\n  const data = Reflect.get(value, \"data\");\n  const hasMore = Reflect.get(value, \"has_more\");\n  const nextBeforeSeq = Reflect.get(value, \"next_before_seq\");\n  if (!Array.isArray(data) || typeof hasMore !== \"boolean\") {\n    throw new Error(\"Thread history returned an invalid response.\");\n  }\n\n  const seenSeqs = new Set<number>();\n  for (const row of data) {\n    const seq =\n      typeof row === \"object\" && row !== null\n        ? Reflect.get(row, \"seq\")\n        : undefined;\n    if (!isValidThreadMessageSeq(seq)) {\n      throw new Error(\"Thread history returned a row with an invalid seq.\");\n    }\n    if (seenSeqs.has(seq)) {\n      throw new Error(\"Thread history returned duplicate seq values.\");\n    }\n    seenSeqs.add(seq);\n  }\n\n  if (\n    (hasMore && !isValidThreadMessageSeq(nextBeforeSeq)) ||\n    (!hasMore && nextBeforeSeq !== null)\n  ) {\n    throw new Error(\n      \"Thread history returned an invalid next_before_seq cursor.\",\n    );\n  }\n\n  return value as ThreadMessagesPageResponse;\n}\n\nexport function getThreadHistoryNextPageParam(\n  lastPage: ThreadMessagesPageResponse,","sourceCodeStart":340,"sourceCodeEnd":376,"githubUrl":"https://github.com/bytedance/deer-flow/blob/1dd6ba1acb03700589994b0366c5d1c7d05e2eff/frontend/src/core/threads/hooks.ts#L340-L376","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Reproduce the failing page (threadId + before cursor) via curl and confirm which seq repeats.","Inspect the backend history query for fan-out (add DISTINCT on the message primary key, or aggregate joined child rows into arrays).","If a data migration produced duplicate seq values, run a repair migration that reassigns unique monotonically increasing seqs per thread.","Add a backend test asserting unique, strictly ordered seqs per page."],"exampleFix":"-- before: join fan-out duplicates message rows\nSELECT m.* FROM messages m JOIN attachments a ON a.message_id = m.id WHERE m.thread_id = :tid;\n\n-- after: dedupe on the message primary key\nSELECT 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;","handlingStrategy":"validation","validationCode":"const seqs = rows.map((r) => r.seq);\nif (new Set(seqs).size !== seqs.length) { /* duplicate seq in page: log and request the page again / fall back to full reload */ }","typeGuard":"function hasUniqueSeqs(rows: Array<{ seq: number }>): boolean {\n  return new Set(rows.map((r) => r.seq)).size === rows.length;\n}","tryCatchPattern":"catch (e) { if (e.message === \"Thread history returned duplicate seq values.\") { await queryClient.invalidateQueries({ queryKey: threadHistoryKey }); return; } throw e; }","preventionTips":["Use DISTINCT or aggregate joins in the history SQL to avoid fan-out duplicates.","Enforce UNIQUE(thread_id, seq) at the DB level so duplicates cannot be written.","Add pagination tests that exercise multi-row pages with joined child data."],"tags":["validation","threads","data-integrity","pagination"],"backgroundTag":null,"analyzedSha":"1dd6ba1acb03700589994b0366c5d1c7d05e2eff","analyzedAt":"2026-08-14T21:20:34.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}