gastownhall/beads · error
get issue comments page from %s (after %v/%q): %w
Error message
get issue comments page from %s (after %v/%q): %w
What it means
GetIssueCommentsPageInTx fails when the keyset-paged comments query (with the sargable created_at >= ? AND ((created_at > ?) OR (id > ?)) predicate when a cursor is set) returns a driver error. The message includes the table and the resume cursor (created_at/id) to aid debugging. Like the unpaginated read, this is an infrastructure/query failure, not a missing issue (a missing issue yields an empty page, no error).
Source
Thrown at internal/storage/issueops/comments.go:139
table := "comments"
if IsActiveWispInTx(ctx, tx, issueID) {
table = "wisp_comments"
}
hasCursor := !after.CreatedAt.IsZero() || after.ID != ""
args := []any{issueID}
if hasCursor {
// Bind the cursor time as time.Time, not a formatted string: created_at
// is a DATETIME column, so a time.Time value compares correctly on every
// backend while an RFC3339 string can mis-compare. Bound twice (the
// sargable lower bound and the strict bound), then the id tie-break.
args = append(args, after.CreatedAt, after.CreatedAt, after.ID)
}
rows, err := tx.QueryContext(ctx, CommentsPageQuery(table, hasCursor, limit), args...)
if err != nil {
return nil, fmt.Errorf("get issue comments page from %s (after %v/%q): %w", table, after.CreatedAt, after.ID, err)
}
defer rows.Close()
var comments []*types.Comment
for rows.Next() {
var c types.Comment
if err := rows.Scan(&c.ID, &c.IssueID, &c.Author, &c.Text, &c.CreatedAt); err != nil {
return nil, fmt.Errorf("get issue comments page: scan: %w", err)
}
comments = append(comments, &c)
}
return comments, rows.Err()
}
// GetCommentCountsInTx returns comment counts per issue ID within a transaction.
// Routes each ID to comments or wisp_comments based on wisp status.
// Uses batched IN clauses (queryBatchSize) to avoid query-planner spikes.
func GetCommentCountsInTx(ctx context.Context, tx *sql.Tx, issueIDs []string) (map[string]int, error) {View on GitHub (pinned to 71377f2769)
Solutions
- Retry the failed page (keyset paging is idempotent for a given cursor).
- Bind the cursor as a time.Time (storage.CommentPageCursor.CreatedAt), never a preformatted string.
- Increase the context timeout per page and re-check the schema/index after migrations.
Example fix
// before
after := storage.CommentPageCursor{CreatedAt: parsedFromString} // string-backed time
// after
ts, err := time.Parse(time.RFC3339, cursorStr)
if err != nil { return err }
after := storage.CommentPageCursor{CreatedAt: ts.UTC(), ID: cursorID} Defensive patterns
Strategy: retry
Validate before calling
if !after.CreatedAt.IsZero() && after.ID == "" {
// partial cursor: refetch the last page to rebuild a complete cursor
} Try / catch
page, err := store.GetIssueCommentsPage(ctx, id, after, limit)
if err != nil {
if isTransientDB(err) {
page, err = store.GetIssueCommentsPage(ctx, id, after, limit) // same cursor = idempotent
}
if err != nil { return err }
} Prevention
- Always build cursors from a previously returned comment's (CreatedAt, ID); never fabricate them from strings.
- Keep limit <= 500 (the clamp max) so paging loops terminate predictably.
- Retry a failed page with the same cursor — keyset paging cannot duplicate or skip on retry.
When it happens
Trigger: Paging a thread when the connection drops or the context is cancelled mid-query; passing a cursor whose CreatedAt is a formatted RFC3339 string rather than a time.Time can also break comparison on some backends; schema changes invalidating the (issue_id, created_at, id) index.
Common situations: UI or bot walking a very long thread across many requests, with timeouts between pages; custom drivers with different DATETIME binding semantics; migrations running while pages are fetched.
Understand the failure class
Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.
Related errors
- failed to begin transaction: %w
- failed to recompute is_blocked: %w
- failed to commit is_blocked repairs: %w
- %s: %w
- failed to query orphaned dependencies: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/12d3b8a7ed17f095.
Report an issue: GitHub.