fix(backend): preserve run message pagination boundary#3188
Open
LittleChenLiya wants to merge 5 commits into
Open
fix(backend): preserve run message pagination boundary#3188LittleChenLiya wants to merge 5 commits into
LittleChenLiya wants to merge 5 commits into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a pagination edge case in the gateway “run messages” APIs where the backend requests limit + 1 rows (to compute has_more) but then trims from the wrong side for “latest page” and before_seq pagination, causing the newest message on a page to be dropped and unrecoverable via subsequent pagination.
Changes:
- Introduces a shared trimming helper that preserves the correct pagination boundary when
limit + 1rows are returned. - Applies the trimming fix to both thread-scoped (
/api/threads/{thread_id}/runs/{run_id}/messages) and run-scoped (/api/runs/{run_id}/messages) message endpoints. - Adds tests to cover default (latest),
before_seq(backward), andafter_seq(forward) pagination behavior with a sentinel extra row.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| backend/app/gateway/routers/thread_runs.py | Adds trim_run_message_page() and uses it so default/latest and before_seq pagination keep the newest messages while still computing has_more. |
| backend/app/gateway/routers/runs.py | Reuses the same trimming logic for the run-scoped messages endpoint for consistent pagination behavior. |
| backend/tests/test_thread_run_messages_pagination.py | Adds assertions/tests ensuring the sentinel limit+1 row does not cause newest-message loss across default/before/after pagination. |
| backend/tests/test_runs_api_endpoints.py | Mirrors the pagination boundary tests for /api/runs/{run_id}/messages to prevent regressions in the run-scoped endpoint. |
This was referenced May 27, 2026
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题原因
run messages 接口为了判断是否还有更多分页,会向 event store 请求
limit + 1条记录。默认加载和before_seq向旧消息分页时,底层返回的是最新一页消息并按升序排列,多出来的一条位于结果开头,表示更早的分页哨兵记录。当前接口统一使用
rows[:limit]裁剪,导致默认分页时丢掉结果末尾的最新消息。如果一个 run 有 66 条 message event,默认limit=50时第一页会漏掉最后一条,第二页再用before_seq也无法取回该消息。修改内容
after_seq正向分页继续保留前limit条。before_seq向旧消息分页改为保留后limit条,避免丢失最新消息。limit + 1哨兵记录不会导致消息缺失。关联 issue
关联 #3052
Problem Cause
The run messages endpoints request
limit + 1rows from the event store to detect whether more pages exist. For default loading andbefore_seqpagination toward older messages, the storage layer returns the latest page in ascending order, and the extra row is at the beginning as an earlier-page sentinel.The endpoints always trimmed with
rows[:limit], which dropped the newest message at the end of the result during default pagination. If a run had 66 message events and the defaultlimit=50was used, the first page missed the last message, and the second page could not recover it withbefore_seq.Changes
after_seqforward pagination preserving the firstlimitrows.before_seqpagination toward older messages to preserve the lastlimitrows, avoiding loss of newest messages.limit + 1sentinel record without message loss.Related Issue
Related to #3052