thedotmack/claude-mem · warning · AppError

INVALID_SEARCH_REQUEST

INVALID_SEARCH_REQUEST

Error message

Either query or filters required for search

What it means

searchObservations() requires either a non-empty query or at least one usable filter. When query is falsy it builds a filter clause from the remaining options; if that clause is empty (no recognized filters contributed), it throws AppError 400 INVALID_SEARCH_REQUEST. This prevents an unbounded SELECT returning the whole observations table.

Source

Thrown at src/services/sqlite/SessionSearch.ts:254

      case 'relevance':
        return hasFTS ? `ORDER BY ${ftsTable}.rank ASC` : 'ORDER BY o.created_at_epoch DESC';
      case 'date_desc':
        return 'ORDER BY o.created_at_epoch DESC';
      case 'date_asc':
        return 'ORDER BY o.created_at_epoch ASC';
      default:
        return 'ORDER BY o.created_at_epoch DESC';
    }
  }

  searchObservations(query: string | undefined, options: SearchOptions = {}): ObservationSearchResult[] {
    const params: any[] = [];
    const { limit = 50, offset = 0, orderBy = 'relevance', ...filters } = options;

    if (!query) {
      const filterClause = this.buildFilterClause(filters, params, 'o');
      if (!filterClause) {
        throw new AppError(SessionSearch.MISSING_SEARCH_INPUT_MESSAGE, 400, 'INVALID_SEARCH_REQUEST');
      }

      const orderClause = this.buildOrderClause(orderBy, false);

      const sql = `
        SELECT o.*, o.discovery_tokens
        FROM observations o
        WHERE ${filterClause}
        ${orderClause}
        LIMIT ? OFFSET ?
      `;

      params.push(limit, offset);
      return this.db.prepare(sql).all(...params) as ObservationSearchResult[];
    }

    if (this._fts5Available) {
      const filterClause = this.buildFilterClause(filters, params, 'o');

View on GitHub (pinned to d768ba3643)

Solutions

  1. Provide a non-empty query string, OR at least one filter the buildFilterClause recognizes (e.g. project, platformSource, dateRange, type).
  2. In calling UI, disable the search action until the user enters a query or picks a filter.
  3. Validate inputs before calling: if neither query nor filters are present, return [] without invoking search.

Example fix

// before
search.searchObservations(undefined, { limit: 50 }); // throws

// after
if (!query && Object.keys(filters).length === 0) return [];
search.searchObservations(query, { ...filters, limit: 50 });
Defensive patterns

Strategy: validation

Validate before calling

import type { SearchOptions } from './types.js';

function hasSearchInput(query?: string, options: SearchOptions = {}): boolean {
  if (query && query.trim()) return true;
  const { limit, offset, orderBy, ...filters } = options;
  return Object.keys(filters).length > 0;
}

// usage
if (!hasSearchInput(query, options)) return [];
search.searchObservations(query, options);

Type guard

import { AppError } from '../server/ErrorHandler.js';

function isInvalidSearchRequest(e: unknown): boolean {
  return e instanceof AppError && e.code === 'INVALID_SEARCH_REQUEST';
}

Try / catch

try {
  return search.searchObservations(query, options);
} catch (e) {
  if (e instanceof AppError && e.code === 'INVALID_SEARCH_REQUEST') return []; // empty input -> empty result
  throw e;
}

Prevention

When it happens

Trigger: Calling searchObservations(undefined, {}) or searchObservations('', { limit: 10 }) — i.e. no query and no filters (or only keys buildFilterClause ignores, plus limit/offset/orderBy which are destructured out).

Common situations: A caller passes an empty search box with no filters selected; a default invocation from a UI that sends {limit,offset} only; a programmatic caller that conditionally omits both query and filters.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12). Data as JSON: /api/errors/7c97a9c1f844b3c8. Report an issue: GitHub.