Repository navigation
Session list "Load More" pagination broken when filtering by directory #30109
Description
Description
Two related bugs found:
Bug 1: GET /session ignores offset when directory is specified
The "Load More" button in the session list does nothing. The frontend loadMore logic increments the limit parameter and re-fetches sessions, but the server ignores offset when directory is present, and limit is capped at the total number of sessions for that directory — so pagination never advances beyond the first page.
Verified via curl:
# Without directory (limit & offset work correctly)
GET /session?limit=5 → 5 sessions
GET /session?limit=50 → 50 sessions
# With directory: limit capped at total, offset completely ignored
GET /session?directory=...&limit=5 → 5 (OK, ≤ 18 total)
GET /session?directory=...&limit=50 → 18 (capped, expected 50)
GET /session?directory=...&limit=200 → 18 (capped, expected 200)
# offset: always returns the first session regardless of value
GET /session?directory=...&offset=0&limit=1 → ses_181a21538...
GET /session?directory=...&offset=10&limit=1 → ses_181a21538... (SAME!)
The frontend code calls client.session.list({ directory, roots: true, limit: N }) with an increasing limit on each "Load More" click. Since offset is ignored and limit is capped, the same data is returned every time — the UI never shows new sessions.
Relevant frontend code path (found in the minified JS bundle served by the OpenCode web server):
// KZ() wraps the session list call with limit
function KZ(t) {
try {
return {
data: (await t.list({directory: t.directory, roots: true, limit: t.limit})).data,
limit: t.limit,
limited: true
}
} catch {
return {
data: (await t.list({directory: t.directory, roots: true})).data,
limit: t.limit,
limited: false
}
}
}The loadMore handler increments limit by 5 and calls loadSessions:
I = async () => {
l("limit", M => (M ?? 0) + 5),
await r.project.loadSessions(t.directory)
}Since offset is ignored and limit is capped at the directory's total sessions, the same data is returned every time and the session count never increases.
Bug 2: /api/session?roots=true crashes on time_archived type mismatch
The cursor-based pagination endpoint GET /api/session works fine for non-archived sessions, but breaks when roots=true causes archived sessions to appear in results:
{
"_tag": "InvalidRequestError",
"message": "Expected DateTime.Utc, got 1780160907969\n at [\"items\"][6][\"time\"][\"archived\"]",
"kind": "Body"
}Why roots=true is the trigger: all 140 archived sessions in the DB are root sessions (no parent_id). Without roots=true, archived sessions are excluded from results and the endpoint works fine. With roots=true, the first archive-eligible root session appears at index 6 and the serializer crashes.
# Works — no archived sessions in results
GET /api/session?limit=10 → 200 OK, items + cursor pagination
# Works (small limit avoids archived sessions)
GET /api/session?roots=true&limit=5 → 200 OK (no archived sessions in first 5)
# Breaks — archived root at index 6
GET /api/session?roots=true&limit=20 → 500 InvalidRequestErrorRoot cause: the session.time_archived column stores Unix millisecond timestamps as integers (e.g., 1780160907969), but the API serializer expects DateTime.Utc format (ISO 8601 string).
Database schema:
`time_archived` integer, -- stored as integer (ms timestamp)Plugins
oh-my-openagent@latest (4.5.12)
OpenCode version
1.15.13
Steps to reproduce
Bug 1 — Pagination params ignored with directory filter:
- Open any project's session list (web UI or TUI)
- Count the initially loaded sessions
- Click "Load More" / "加载更多"
- Nothing happens — the button appears to be unresponsive, no new sessions load
Bug 2 — /api/session?roots=true datetime parse error:
- Archive a session (sets
time_archivedto integer ms timestamp in DB) - Request
GET /api/session?roots=true&limit=20 - Receive
InvalidRequestError—Expected DateTime.Utc, got <integer> - Note: without
roots=truethe endpoint works fine
Screenshot and/or share link
N/A (API-level bugs, reproducible via curl)
Operating System
Ubuntu 26.04 (WSL)
Terminal
N/A (Tested via web UI + curl)
Activity
lexlian commentedon Jun 1, 2026
I fixed the issue with PR #30217 — turned out to be two unrelated bugs in the session list, both worth landing in one PR.
Bug 1 — "Load More" is stuck. With
directory=set,GET /session?offset=N&limit=Lwas returning the same first page no matter whatoffsetyou sent. The frontend's "Load More" was effectively a no-op whenever you were scoped to a directory.offsetwas never declared onListQuery, never onListInput, never read offctx.queryin the handler, and never applied to the database query inlistByProject— every layer silently dropped it, so the server was always just running the same "give me the firstlimitrows" query regardless of what the client sent.offsetthrough all four layers — added the field toListQueryandListInput, forwarded it in the handler, and added the matchingOFFSETto the SQL so the server actually skips past the firstoffsetrows.Bug 2 —
?roots=truereturns 500. The v2Info.time.archivedfield was wrapped inoptionalOmitUndefined(V2Schema.DateTimeUtcFromMillis), and any query that hit an archived root session blew up withExpected DateTime.Utc, got 1780160907969. The v1 path was fine, which is why this only surfaced on the v2 endpoint.DateTimeUtcFromMillis, which is itself a schema that converts between anumber(epoch millis) and aDateTime.Utcvalue in both directions. The bug is in howoptionalOmitUndefinedcomposes with that — when it builds the optional wrapper, it accidentally double-applies the number-to-DateTime conversion, so by the time the outer encoder runs it's already looking at anumberbut expecting aDateTime.Utcand throws. The v1Info.time.archiveddoesn't have this problem because it wraps plainSchema.Finite(a number is a number, no conversion involved), so the wrapper just works as expected. Only the v2 wrapper aroundDateTimeUtcFromMillishits the edge case.Schema.optional(...)to match whatpackages/core/src/session/message.tsalready does fortime.completed/time.ran/time.pruned. The wire format is now identical to the bareDateTimeUtcFromMillisfield, no observable change for working inputs.github-actions commentedon Aug 1, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.