mem0ai/mem0 · error · Error

Invalid filter key: ${JSON.stringify(key)}

Error message

Invalid filter key: ${JSON.stringify(key)}

What it means

createFilter() interpolates filter keys into a Milvus boolean expression as metadata["<key>"], so a key containing characters outside [a-zA-Z_][a-zA-Z0-9_]* could alter or inject expression syntax. Keys are validated against SAFE_FILTER_KEY and rejected with JSON.stringify(key) so the offending key is visible in the error. This mirrors the Python provider's _create_filter.

Source

Thrown at mem0-ts/src/oss/src/vector_stores/milvus.ts:236

  /**
   * Filter keys are interpolated straight into the expression, so restrict them
   * to safe identifiers (same rule as the Python provider) to block injection.
   */
  private static readonly SAFE_FILTER_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/;

  /**
   * Build a Milvus boolean filter expression from a flat filters object.
   * Mirrors the Python `_create_filter` (equality only, AND-combined): validate
   * each key, escape string values (backslash first, then double-quote), and
   * reject value types Milvus can't compare against a scalar field.
   */
  private createFilter(filters?: SearchFilters): string | undefined {
    if (!filters || Object.keys(filters).length === 0) return undefined;
    const operands: string[] = [];
    for (const [key, value] of Object.entries(filters)) {
      if (value === undefined || value === null) continue;
      if (!Milvus.SAFE_FILTER_KEY.test(key)) {
        throw new Error(`Invalid filter key: ${JSON.stringify(key)}`);
      }
      if (value === "*") {
        // Wildcard - match any value. Milvus has no direct wildcard, so skip
        // the clause rather than emitting a literal `== "*"` that matches
        // nothing. Mirrors the Python provider (#6187) and the chroma/pinecone
        // stores.
        continue;
      }
      if (typeof value === "string") {
        // Escape backslashes before quotes so a value can't break out of the
        // string literal (order matters, exactly as in the Python provider).
        const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
        operands.push(`(metadata["${key}"] == "${escaped}")`);
      } else if (typeof value === "number" || typeof value === "boolean") {
        operands.push(`(metadata["${key}"] == ${value})`);
      } else {
        throw new Error(
          `Filter value for ${JSON.stringify(key)} must be a string, number, or boolean, got ${typeof value}`,

View on GitHub (pinned to 001c235229)

Solutions

  1. Rename filter keys to match ^[a-zA-Z_][a-zA-Z0-9_]*$ (snake_case is the repo convention: user_id, agent_id, run_id).
  2. Map external keys to safe keys before calling search: { user_id: external['user-id'] }.
  3. Sanitize keys at your API boundary: key.replace(/[^a-zA-Z0-9_]/g, '_') with a leading-underscore fix if it starts with a digit.

Example fix

// before
store.search(q, 5, { 'user-id': 'u1' }); // throws: Invalid filter key

// after
store.search(q, 5, { user_id: 'u1' });
Defensive patterns

Strategy: type-guard

Validate before calling

const SAFE_KEY = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
const badKeys = Object.keys(filters || {}).filter((k) => !SAFE_KEY.test(k));
if (badKeys.length) throw new Error(`Unsafe Milvus filter keys: ${badKeys.join(', ')}`);

Type guard

const isSafeFilterKey = (k: string): boolean => /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(k);
function assertSafeMilvusFilters(f: Record<string, unknown>): void {
  for (const k of Object.keys(f)) if (!isSafeFilterKey(k)) throw new Error(`Invalid filter key: ${k}`);
}

Try / catch

try { await store.search(q, 5, filters); }
catch (e) {
  if (e instanceof Error && e.message.startsWith('Invalid filter key')) {
    // sanitize keys (replace [^a-zA-Z0-9_] with '_') and retry once
  } else throw e;
}

Prevention

When it happens

Trigger: Passing filters with keys like 'user-id', 'user id', 'user.id', 'user:id', keys starting with a digit ('1key'), empty-string keys, or non-string keys after Object.entries coercion; derived keys built from user input containing dashes or dots.

Common situations: Using hyphenated or dotted metadata field names (common in JSON from external systems); slugifying filter keys to kebab-case; migrating metadata schemas from systems that allow arbitrary key characters.

Related errors


AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15). Data as JSON: /api/errors/29f86c28b0206d65. Report an issue: GitHub.