multica-ai/multica · warning
invalid cursor
Error message
invalid cursor
What it means
This error comes from parseChatMessagesPageParams when exactly one of the two cursor components is supplied: the endpoint requires `before_created_at` and `before_id` to be sent together as a pair for keyset (seek) pagination. Supplying only one half makes the cursor ambiguous — the server cannot do the (created_at, id) tuple comparison without both — so it rejects rather than guess. Both absent is fine (first page); both present is the only valid cursor form.
Source
Thrown at server/internal/handler/chat.go:1005
}
func parseChatMessagesPageParams(r *http.Request) (int, pgtype.Timestamptz, pgtype.UUID, error) {
limit := 50
if raw := r.URL.Query().Get("limit"); raw != "" {
parsed, err := strconv.Atoi(raw)
if err != nil || parsed < 1 || parsed > 100 {
return 0, pgtype.Timestamptz{}, pgtype.UUID{}, errors.New("invalid limit")
}
limit = parsed
}
rawBeforeCreatedAt := r.URL.Query().Get("before_created_at")
rawBeforeID := r.URL.Query().Get("before_id")
if rawBeforeCreatedAt == "" && rawBeforeID == "" {
return limit, pgtype.Timestamptz{}, pgtype.UUID{}, nil
}
if rawBeforeCreatedAt == "" || rawBeforeID == "" {
return 0, pgtype.Timestamptz{}, pgtype.UUID{}, errors.New("invalid cursor")
}
beforeTime, err := time.Parse(time.RFC3339Nano, rawBeforeCreatedAt)
if err != nil {
return 0, pgtype.Timestamptz{}, pgtype.UUID{}, errors.New("invalid cursor")
}
beforeID, err := util.ParseUUID(rawBeforeID)
if err != nil {
return 0, pgtype.Timestamptz{}, pgtype.UUID{}, errors.New("invalid cursor")
}
return limit, pgtype.Timestamptz{Time: beforeTime, Valid: true}, beforeID, nil
}
// RegenerateChatQuickActionsResponse acknowledges an accepted refresh request.
// message_id is the assistant turn the refreshed pills will attach to — the
// client anchors its pending placeholder on it and resolves it when the
// chat:quick_actions supplement arrives.
// RegenerateChatQuickActionsRequest names the assistant turn the client is
// refreshing. The server confirms it is still the session's latest turn beforeView on GitHub (pinned to 2c0912b6ec)
Solutions
- Use the `next_cursor` object from the previous response verbatim — it contains both fields as a pair.
- Send both `before_created_at` and `before_id`, or neither.
- Audit client code that builds query strings conditionally and make the two fields emit together.
- If implementing pagination from scratch, keyset-paginate on the (created_at, id) tuple returned in the page payload.
Example fix
// before
const url = `/api/chat/${id}/messages?before_created_at=${encodeURIComponent(c.created_at)}`;
// after
const url = `/api/chat/${id}/messages?before_created_at=${encodeURIComponent(c.created_at)}&before_id=${encodeURIComponent(c.id)}`; Defensive patterns
Strategy: validation
Validate before calling
function cursorParams(cursor) {
if (!cursor) return '';
const { created_at, id } = cursor ?? {};
const present = [created_at, id].filter(v => v !== undefined && v !== '').length;
if (present !== 0 && present !== 2) {
throw new TypeError('cursor must include both before_created_at and before_id, or neither');
}
return present === 2 ? `before_created_at=${encodeURIComponent(created_at)}&before_id=${encodeURIComponent(id)}` : '';
} Type guard
const isCompleteCursor = (c) => c == null || (typeof c?.created_at === 'string' && typeof c?.id === 'string' && c.created_at !== '' && c.id !== '');
Prevention
- Persist the whole next_cursor object from the response; never rebuild it from parts.
- Disable HTTP-client options that drop empty-string query params when composing cursor URLs.
- Integration-test pagination by walking 3+ pages using only returned cursors.
When it happens
Trigger: GET with `?before_created_at=2024-01-01T00:00:00Z` but no `before_id`, or `?before_id=<uuid>` with no `before_created_at`. Typically caused by manually assembling cursor URLs or by a client that drops one field when serializing the next_cursor object.
Common situations: Custom client implementing pagination from API docs and sending only the timestamp; a serialization bug where an empty-string before_id is omitted by an HTTP library that skips empty params; bookmarking a truncated URL.
Related errors
- invalid limit
- runtime probe_result must be success or error
- failed runtime probes must not include counts
- successful runtime probes require all counts
- invalid runtime counts
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/f61aba1e5af1f741.
Report an issue: GitHub.