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 before

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Use the `next_cursor` object from the previous response verbatim — it contains both fields as a pair.
  2. Send both `before_created_at` and `before_id`, or neither.
  3. Audit client code that builds query strings conditionally and make the two fields emit together.
  4. 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

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


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/f61aba1e5af1f741. Report an issue: GitHub.