Skip to content

fix(backend/copilot): fix initial load missing messages + forward pagination for completed sessions - #12796

Merged
majdyz merged 47 commits into
devfrom
fix/copilot-pagination-initial-load-missing-messages
Apr 16, 2026
Merged

fix(backend/copilot): fix initial load missing messages + forward pagination for completed sessions#12796
majdyz merged 47 commits into
devfrom
fix/copilot-pagination-initial-load-missing-messages

Conversation

@majdyz

@majdyz majdyz commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Why / What / How

Why: Completed copilot sessions with many messages showed a completely empty chat view. A user reported a 158-message session that appeared blank on reload.

What: Two bugs fixed:

  1. Backend — initial page load always returned the newest 50 messages in DESC order. For sessions heavy in tool calls, the user's original messages (seq 0–5) were never included; all 50 slots consumed by mid-session tool outputs.
  2. Frontend — convertChatSessionToUiMessages silently dropped user messages with null/empty content.

How: For completed sessions (no active stream), the backend now loads from sequence 0 in ASC order. Active/streaming sessions keep newest-first for streaming context. A new after_sequence forward cursor enables infinite-scroll for subsequent pages (sentinel moves to bottom). The frontend wires forward_paginated + newest_sequence end-to-end.

Changes 🏗️

  • db.py: added from_start (ASC) and after_sequence (forward cursor) modes; added newest_sequence to PaginatedMessages
  • routes.py: detect completed vs active on initial load; pass from_start=True for completed; expose newest_sequence + forward_paginated; accept after_sequence param
  • convertChatSessionToUiMessages.ts: never drop user messages with empty content
  • useLoadMoreMessages.ts: forward pagination via after_sequence; append pages to end
  • ChatMessagesContainer.tsx: LoadMoreSentinel at bottom for forward-paginated sessions
  • Wire newestSequence + forwardPaginated end-to-end through useChatSession/useCopilotPage/ChatContainer
  • openapi.json: add after_sequence + newest_sequence/forward_paginated; regenerate types
  • db_test.py: 9 new unit tests for from_start and after_sequence modes

Checklist 📋

For code changes:

  • I have clearly listed my changes in the PR description
  • I have made a test plan
  • I have tested my changes according to the test plan:
    • Open a completed session with many messages — first user message visible on initial load
    • Scroll to bottom of completed session — load more appends next page
    • Open active/streaming session — newest messages shown first, streaming unaffected
    • Backend unit tests: all 28 pass
    • Frontend lint/format: clean, no new type errors

…ination for completed sessions

Completed copilot sessions with many messages were showing an empty view
because the backend returned only the newest 50 (all tool calls, no user
messages) and the frontend silently dropped messages with empty content.

Backend changes:
- get_chat_messages_paginated: add from_start (ASC) and after_sequence
  (forward cursor) modes alongside the existing before_sequence (DESC)
  backward mode
- PaginatedMessages: expose newest_sequence for forward-pagination cursors
- routes.py: detect completed sessions on initial load (no active stream)
  and use from_start=True; expose newest_sequence + forward_paginated in
  SessionDetailResponse; accept after_sequence query param
- openapi.json: add after_sequence param + newest_sequence / forward_paginated
  fields to SessionDetailResponse schema

Frontend changes:
- convertChatSessionToUiMessages: never drop user messages with empty content
- useLoadMoreMessages: support forward pagination via after_sequence cursor;
  append pages to end rather than prepending for completed sessions
- ChatMessagesContainer: move LoadMoreSentinel to bottom for forward pagination
- useChatSession / useCopilotPage / ChatContainer: wire up newestSequence and
  forwardPaginated props end-to-end

Tests: add 9 new unit tests for from_start and after_sequence pagination modes
@majdyz
majdyz requested a review from a team as a code owner April 15, 2026 12:46
@majdyz
majdyz requested review from 0ubbe and Bentlybro and removed request for a team April 15, 2026 12:46
@github-project-automation github-project-automation Bot moved this to 🆕 Needs initial review in AutoGPT development kanban Apr 15, 2026
@majdyz

majdyz commented Apr 15, 2026

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot added platform/frontend AutoGPT Platform - Front end platform/backend AutoGPT Platform - Back end labels Apr 15, 2026
@autogpt-pr-reviewer

Copy link
Copy Markdown

Queued a review for PR #12796 at 8546f67.

@github-actions

github-actions Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

🔍 PR Overlap Detection

This check compares your PR against all other open PRs targeting the same branch to detect potential merge conflicts early.

🔴 Merge Conflicts Detected

The following PRs have been tested and will have merge conflicts if merged after this PR. Consider coordinating with the authors.

🟡 Medium Risk — Some Line Overlap

These PRs have some overlapping changes:

🟢 Low Risk — File Overlap Only

These PRs touch the same files but different sections (click to expand)

Summary: 3 conflict(s), 2 medium risk, 5 low risk (out of 10 PRs with file overlap)


Auto-generated on push. Ignores: openapi.json, lock files.

Comment thread autogpt_platform/backend/backend/api/features/chat/routes.py
Comment thread autogpt_platform/backend/backend/api/features/chat/routes.py Outdated
Comment thread autogpt_platform/frontend/src/app/api/openapi.json Outdated
Comment thread autogpt_platform/frontend/src/app/api/openapi.json Outdated
@codecov

codecov Bot commented Apr 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.87755% with 18 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.41%. Comparing base (0d4b31e) to head (46793a7).
⚠️ Report is 14 commits behind head on dev.

Additional details and impacted files
@@            Coverage Diff             @@
##              dev   #12796      +/-   ##
==========================================
+ Coverage   65.14%   65.41%   +0.27%     
==========================================
  Files        1831     1850      +19     
  Lines      135945   137462    +1517     
  Branches    14534    14735     +201     
==========================================
+ Hits        88555    89923    +1368     
- Misses      44670    44747      +77     
- Partials     2720     2792      +72     
Flag Coverage Δ
platform-backend 76.03% <97.78%> (+0.18%) ⬆️
platform-frontend 20.74% <83.07%> (+1.39%) ⬆️
platform-frontend-e2e 30.29% <28.57%> (+0.27%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Platform Backend 76.03% <97.78%> (+0.18%) ⬆️
Platform Frontend 28.33% <80.88%> (+1.10%) ⬆️
AutoGPT Libs ∅ <ø> (∅)
Classic AutoGPT 28.43% <ø> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread autogpt_platform/frontend/src/app/(platform)/copilot/useLoadMoreMessages.ts Outdated
@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds forward cursor pagination: backend accepts after_sequence, determines pagination direction on initial load via active-stream lookup, and returns newest_sequence and forward_paginated. Frontend threads forwardPaginated through hooks and components, moves load-more sentinel, and switches paged-message accumulation and merge order.

Changes

Cohort / File(s) Summary
Backend DB & Tests
autogpt_platform/backend/backend/copilot/db.py, autogpt_platform/backend/backend/copilot/db_test.py
Added newest_sequence to PaginatedMessages. get_chat_messages_paginated accepts after_sequence/from_start for forward (ASC) paging and preserves before_sequence for backward (DESC); ordering, reversal, and tool-boundary expansion are conditional on direction. Tests for forward/backward ordering, cursors, and boundary-query behavior added.
Backend API Routes & Tests
autogpt_platform/backend/backend/api/features/chat/routes.py, autogpt_platform/backend/backend/api/features/chat/routes_test.py
get_session now accepts after_sequence and validates cursors (rejects both). SessionDetailResponse gains newest_sequence and forward_paginated. Initial-load active-stream lookup added to decide pagination direction. Tests added for direction, metadata, and input validation.
Frontend Hooks & Logic
autogpt_platform/frontend/src/app/(platform)/copilot/useChatSession.ts, .../useCopilotPage.ts, .../useLoadMoreMessages.ts
Session hook exposes newestSequence and forwardPaginated. useCopilotPage/useLoadMoreMessages use initialNewestSequence and forwardPaginated; accumulated state renamed to pagedRawMessages/pagedMessages; load-more uses after_sequence (forward) vs before_sequence (backward) and merges pages accordingly.
Frontend Components
autogpt_platform/frontend/src/app/(platform)/copilot/CopilotPage.tsx, .../ChatContainer/ChatContainer.tsx, .../ChatMessagesContainer/ChatMessagesContainer.tsx
Threaded forwardPaginated prop through components. ChatMessagesContainer renders LoadMore sentinel at top for backward pagination and at bottom for forward pagination; LoadMoreSentinel accepts configurable rootMargin and messageCount.
Frontend Message Conversion & Tests
autogpt_platform/frontend/src/app/(platform)/copilot/helpers/convertChatSessionToUiMessages.ts, .../__tests__/convertChatSessionToUiMessages.test.ts
Preserve user messages with empty/null content by emitting a default empty text part; added tests validating preservation/filtration behavior for user vs assistant empty content.
OpenAPI / Schema
autogpt_platform/frontend/src/app/api/openapi.json
Added optional after_sequence query param to /api/chat/sessions/{session_id} and extended SessionDetailResponse with newest_sequence and forward_paginated.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant UI as Chat UI
    participant Hook as useCopilotPage
    participant Session as useChatSession
    participant API as Backend API
    participant DB as Database

    User->>UI: Open session / resume
    UI->>Hook: initial render
    Hook->>Session: request session (no cursors)
    Session->>API: GET /api/chat/sessions/{id}?before_sequence=None&after_sequence=None
    API->>DB: lookup active stream / session state
    DB-->>API: active vs completed
    alt Active
        API->>DB: fetch messages (DESC newest-first)
        DB-->>API: messages + oldest_sequence
        API-->>Session: SessionDetailResponse(forward_paginated=false, oldest_sequence=...)
    else Completed
        API->>DB: fetch messages (ASC from start / from_start)
        DB-->>API: messages + newest_sequence
        API-->>Session: SessionDetailResponse(forward_paginated=true, newest_sequence=...)
    end
    Session-->>Hook: messages + forwardPaginated
    Hook-->>UI: render messages

    User->>UI: scroll -> load more
    UI->>Hook: onLoadMore()
    alt forward_paginated=true
        Hook->>Session: request after_sequence=X
        Session->>API: GET /api/chat/sessions/{id}?after_sequence=X
        API->>DB: fetch messages ASC where sequence > X
        DB-->>API: newer messages
        API-->>Session: paged messages (append)
    else
        Hook->>Session: request before_sequence=Y
        Session->>API: GET /api/chat/sessions/{id}?before_sequence=Y
        API->>DB: fetch messages DESC where sequence < Y
        DB-->>API: older messages
        API-->>Session: paged messages (prepend)
    end
    Session-->>Hook: updated pagedMessages
    Hook-->>UI: rerender with merged messages
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

size/l

Suggested reviewers

  • Pwuts
  • Bentlybro
  • 0ubbe

Poem

🐇 I hopped through cursors, forward and back,
I checked for active streams along the track.
Newest or oldest, pages now flow,
Sentinels top or bottom — watch them go! 🌿

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.16% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title accurately summarizes the main changes: fixing missing messages on initial load and enabling forward pagination for completed sessions.
Description check ✅ Passed The pull request description is well-related to the changeset, providing clear context about the bugs being fixed, specific changes made across multiple files, and a manual test plan.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/copilot-pagination-initial-load-missing-messages

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…ompact openapi.json

- routes.py: replace f-string log with %s lazy formatting
- routes.py: compute forward_paginated once and reuse across both return paths
- openapi.json: revert reformatting noise, keep only the 3 new field additions
  (newest_sequence, forward_paginated on SessionDetailResponse;
   after_sequence query param on GET /sessions/{id})
@CLAassistant

CLAassistant commented Apr 15, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@github-actions github-actions Bot added size/l and removed size/xl labels Apr 15, 2026
majdyz added 3 commits April 16, 2026 18:30
…o avoid React-18 batching bug

The functional updater passed to setPagedRawMessages is not called
synchronously in React 18 (automatic batching) — mutations inside it
are invisible until the next render. Using `mergedLength = 0` and
reading it after `setPagedRawMessages(fn)` always saw 0, so
hasMore was never forced false for backward pagination at the
MAX_OLDER_MESSAGES cap.

Replace with `estimatedTotal = pagedRawMessages.length + newRaw.length`
computed before the state update using the stale-but-correct closure
value. Worst case: one extra page loaded before the cap triggers.
@majdyz

majdyz commented Apr 16, 2026

Copy link
Copy Markdown
Contributor Author

Round 5 fix — React 18 batching bug in MAX_OLDER_MESSAGES guard

The mergedLength approach introduced in round 4 had a subtle React 18 batching bug.

Root cause: setPagedRawMessages(fn) in React 18 does not call fn synchronously — the functional updater runs during batch reconciliation, after the current microtask queue flushes. So mergedLength remained 0 when the if (mergedLength >= MAX_OLDER_MESSAGES) check ran immediately after:

```ts
let mergedLength = 0;
setPagedRawMessages((prev) => {
mergedLength = merged.length; // runs later, during reconciliation
return merged;
});
// mergedLength is still 0 here — React hasn't run the updater yet
if (mergedLength >= MAX_OLDER_MESSAGES) { ... } // never true
```

Fix: Compute the estimate before the state update using the closure-captured pagedRawMessages.length:

```ts
const estimatedTotal = pagedRawMessages.length + newRaw.length;
setPagedRawMessages((prev) => { ... }); // updater still handles actual slicing
// estimatedTotal is accurate at check time
if (!forwardPaginated && estimatedTotal >= MAX_OLDER_MESSAGES) {
setHasMore(false);
}
```

Worst-case: the closure value is one render stale (one extra load allowed before cap). This is acceptable — the cap is a memory safety guard, not a hard cutoff. The pagedMessages state correctly re-renders after the updater runs.

All 20 frontend unit tests + 30 backend tests pass. Commit: 5681867a83.

…pletes

When a session transitions from active (forwardPaginated=false) to complete
(forwardPaginated=true), backward-paginated older messages would be appended
after currentMessages instead of before, causing chronological disorder.
Add a useEffect that calls resetPaged() on the false→true transition.
@majdyz
majdyz force-pushed the fix/copilot-pagination-initial-load-missing-messages branch from 562a546 to 507055f Compare April 16, 2026 11:57
majdyz added 2 commits April 16, 2026 19:18
… and error paths

- useCopilotPage: test message ordering (forward/backward) and resetPaged
  on forwardPaginated false→true transition
- useLoadMoreMessages: cover setHasMore=false after 3 non-200 responses
  and epoch guard in catch block (stale error discard)
Comment thread autogpt_platform/frontend/src/app/(platform)/copilot/useLoadMoreMessages.ts Outdated
…l items are re-fetched

When forward pagination causes merged.length > MAX_OLDER_MESSAGES, the tail
items are dropped but the cursor (newestSequence) was still advanced to the
server's newest_sequence. If the server also reported has_more_messages=false,
those discarded items became permanently inaccessible.

Fix: when truncation occurs, compute the last KEPT item's sequence from the
raw response and set newestSequence to that value, then force hasMore=true so
the sentinel re-fetches the discarded items on the next load-more. The
fallback when pagedRawMessages is already at MAX_OLDER_MESSAGES (lastKeptIdx<0)
sets hasMore=false to prevent an infinite re-fetch loop at the display cap.
@majdyz
majdyz added this pull request to the merge queue Apr 16, 2026
@github-project-automation github-project-automation Bot moved this from 🚧 Needs work to 👍🏼 Mergeable in AutoGPT development kanban Apr 16, 2026
Merged via the queue into dev with commit 87e4d42 Apr 16, 2026
44 checks passed
@majdyz
majdyz deleted the fix/copilot-pagination-initial-load-missing-messages branch April 16, 2026 14:28
@github-project-automation github-project-automation Bot moved this to Done in Frontend Apr 16, 2026
@github-project-automation github-project-automation Bot moved this from 👍🏼 Mergeable to ✅ Done in AutoGPT development kanban Apr 16, 2026
majdyz added a commit that referenced this pull request Apr 17, 2026
…antee for blank chat

PR #12796 changed completed sessions to load from sequence 0 forward,
which broke the standard chat UX (users land at the beginning instead
of the end). This reverts the forward pagination approach and instead
adds a visibility guarantee: after fetching the newest messages, if the
entire page is tool messages (invisible in UI), expand backward until at
least one user/assistant message is included so the chat never appears
blank.

Backend:
- Remove after_sequence, from_start, forward_paginated, newest_sequence
- Always use backward (newest-first) pagination for all sessions
- Add _expand_for_visibility helper that scans up to 200 messages back
- Extract _expand_tool_boundary helper from inline code

Frontend:
- Remove forwardPaginated/newestSequence plumbing from all hooks
- Remove bottom LoadMoreSentinel and forward pagination props
- Simplify useLoadMoreMessages to backward-only
- Always prepend paged messages before current messages
majdyz added a commit that referenced this pull request Apr 17, 2026
…antee for blank chat (#12831)

## Why / What / How

**Why:** PR #12796 changed completed copilot sessions to load messages
from sequence 0 forward (ascending), which broke the standard chat UX —
users now land at the beginning of the conversation instead of the most
recent messages. Reported in Discord.

**What:** Reverts the forward pagination approach and replaces it with a
visibility guarantee that ensures every page contains at least one
user/assistant message.

**How:**
- **Backend**: Removed after_sequence, from_start, forward_paginated,
newest_sequence — always use backward (newest-first) pagination. Added
_expand_for_visibility() helper: after fetching, if the entire page is
tool messages (invisible in UI), expand backward up to 200 messages
until a visible user/assistant message is found.
- **Frontend**: Removed all forwardPaginated/newestSequence plumbing
from hooks and components. Removed bottom LoadMoreSentinel. Simplified
message merge to always prepend paged messages.

### Changes
- routes.py: Reverted to simple backward pagination, removed TOCTOU
re-fetch logic
- db.py: Removed forward mode, extracted _expand_tool_boundary() and
added _expand_for_visibility()
- SessionDetailResponse: Removed newest_sequence and forward_paginated
fields
- openapi.json: Removed after_sequence param and forward pagination
response fields
- Frontend hooks/components: Removed forward pagination props and logic
(-1000 lines)
- Updated all tests (backend: 63 pass, frontend: 1517 pass)

### Checklist
- [x] I have clearly listed my changes in the PR description
- [x] Backend unit tests: 63 pass
- [x] Frontend unit tests: 1517 pass
- [x] Frontend lint + types: clean
- [x] Backend format + pyright: clean
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform/backend AutoGPT Platform - Back end platform/frontend AutoGPT Platform - Front end size/xl

Projects

Status: ✅ Done
Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants