multica-ai/multica · warning

invalid limit

Error message

invalid limit

What it means

This error is returned by parseChatMessagesPageParams when the optional `limit` query parameter on the chat messages listing endpoint fails to parse as an integer, or parses to a value outside the allowed 1..100 window. The server defaults to 50 when the parameter is absent, so the error only fires on a malformed or out-of-range explicit value. It exists to cap page size and prevent non-numeric input from reaching the SQL query layer.

Source

Thrown at server/internal/handler/chat.go:994

type ChatMessagesCursorResponse struct {
	CreatedAt string `json:"created_at"`
	ID        string `json:"id"`
}

type ChatMessagesPageResponse struct {
	Messages   []ChatMessageResponse       `json:"messages"`
	Limit      int                         `json:"limit"`
	HasMore    bool                        `json:"has_more"`
	NextCursor *ChatMessagesCursorResponse `json:"next_cursor,omitempty"`
}

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 {

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Clamp the requested limit client-side to the 1..100 range before building the URL (e.g. Math.min(Math.max(limit,1),100)).
  2. Omit the limit parameter entirely when you want the default of 50.
  3. Ensure the limit is serialized as an integer string, not a float or scientific-notation value.
  4. Treat the 400 response as non-retryable: fix the input, do not backoff-and-retry.

Example fix

// before
const url = `/api/chat/${id}/messages?limit=${userLimit}`;

// after
const clamped = Math.min(Math.max(Number(userLimit) || 50, 1), 100);
const url = `/api/chat/${id}/messages?limit=${clamped}`;
Defensive patterns

Strategy: validation

Validate before calling

function buildMessagesUrl(chatId, { limit, cursor } = {}) {
  const params = new URLSearchParams();
  if (limit !== undefined) {
    const n = Number(limit);
    if (!Number.isInteger(n) || n < 1 || n > 100) {
      throw new RangeError(`limit must be an integer in [1,100], got ${limit}`);
    }
    params.set('limit', String(n));
  }
  if (cursor) {
    if (!cursor.created_at || !cursor.id) throw new TypeError('cursor requires both created_at and id');
    params.set('before_created_at', cursor.created_at);
    params.set('before_id', cursor.id);
  }
  return `/api/chat/${chatId}/messages${params.size ? '?' + params : ''}`;
}

Type guard

const isValidLimit = (v) => Number.isInteger(v) && v >= 1 && v <= 100;

Prevention

When it happens

Trigger: GET /chat messages endpoint with `?limit=0`, `?limit=101`, `?limit=-5`, `?limit=abc`, or `?limit=50.5` (strconv.Atoi rejects floats). Any value that is not a base-10 integer in [1,100] triggers it; omitting limit entirely does not.

Common situations: Client UI letting users pick an unbounded 'load N messages' value; copy-pasting a cursor URL and hand-editing limit; a frontend sending limit as a float string after JSON serialization; automated scrapers probing with limit=1000.

Related errors


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