bytedance/deer-flow · error
Thread history returned an invalid next_before_seq cursor.
Error message
Thread history returned an invalid next_before_seq cursor.
What it means
Final contract check in parseThreadMessagesPageResponse: the 'next_before_seq' cursor must be internally consistent with 'has_more'. If has_more is true, next_before_seq must be a valid seq (so the next page can be fetched); if has_more is false, it must be exactly null. Any other combination throws.
Source
Thrown at frontend/src/core/threads/hooks.ts:367
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,
): number | undefined {
if (!lastPage.has_more) {
return undefined;
}
if (lastPage.next_before_seq === null) {
console.warn(
"Thread history returned has_more without next_before_seq; pagination cannot continue.",
);
return undefined;View on GitHub (pinned to 1dd6ba1acb)
Solutions
- Check the raw JSON for the failing page: what are has_more and next_before_seq?
- Fix the backend to emit next_before_seq = (has_more ? last_row_seq : null).
- If has_more is derived from len(rows) == limit, verify the cursor variable is set on that same branch.
- Add a contract test covering both terminal and non-terminal pages.
Example fix
# before: cursor always emitted
return {"data": rows, "has_more": has_more, "next_before_seq": rows[-1]["seq"]}
# after: cursor only when another page exists
return {"data": rows, "has_more": has_more, "next_before_seq": rows[-1]["seq"] if has_more else None} Defensive patterns
Strategy: validation
Validate before calling
const okCursor = body.has_more ? Number.isInteger(body.next_before_seq) && body.next_before_seq > 0 : body.next_before_seq === null;
if (!okCursor) { /* reject page before handing it to the infinite query */ } Type guard
function cursorConsistent(hasMore: boolean, next: unknown): boolean {
if (hasMore) { return typeof next === "number" && Number.isInteger(next) && next > 0; }
return next === null;
} Try / catch
catch (e) { if (e.message.includes("next_before_seq")) { stopPaginationAndLog(threadId, e); } throw e; } Prevention
- Backend: derive next_before_seq and has_more in one code path so they cannot disagree.
- Contract-test both terminal and non-terminal pages in CI.
- Treat cursor/has_more divergence as a paging bug, never silently clamp it client-side.
When it happens
Trigger: Backend sends has_more=true with next_before_seq null or 0/invalid (e.g. it computed 'more pages' but never derived the cursor), or sends has_more=false with a leftover integer cursor (e.g. cursor computed unconditionally from the last row).
Common situations: Off-by-one pagination logic after a limit change, a serializer that always emits the last row's seq regardless of has_more, or a backend refactor that dropped cursor computation but kept the has_more heuristic.
Related errors
- Thread history returned a row with an invalid seq.
- Thread history returned duplicate seq values.
- Thread history returned an invalid response.
- reuse_thread requires thread_id
- cursor must be >= 0 and limit must be >= 1
AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14).
Data as JSON: /api/errors/6dd07b17b3baae3a.
Report an issue: GitHub.