{"record":{"id":"e675a3442cd83f11","repo":"mem0ai/mem0","slug":"key-filter-value-must-be-an-array-e675a3","errorCode":null,"errorMessage":"${key} filter value must be an array.","messagePattern":"(.+?) filter value must be an array\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"mem0-ts/src/oss/src/vector_stores/opensearch.ts","lineNumber":518,"sourceCode":"    const normalized: Record<string, any> = {};\n    for (const [key, value] of Object.entries(filters)) {\n      normalized[KEY_MAP[key] || key] = value;\n    }\n\n    return Object.entries(normalized)\n      .map(([key, value]) => this.buildFilterClause(key, value))\n      .filter((clause): clause is Record<string, any> => Boolean(clause));\n  }\n\n  private buildFilterClause(\n    key: string,\n    value: any,\n  ): Record<string, any> | null {\n    if (value === null || value === undefined) return null;\n\n    if (key === \"AND\" || key === \"OR\" || key === \"NOT\") {\n      if (!Array.isArray(value)) {\n        throw new Error(`${key} filter value must be an array.`);\n      }\n\n      const clauses = value\n        .flatMap((filter) => this.buildFilterClauses(filter))\n        .filter(Boolean);\n\n      if (clauses.length === 0) return null;\n\n      if (key === \"AND\") {\n        return { bool: { filter: clauses } };\n      }\n      if (key === \"OR\") {\n        return { bool: { should: clauses, minimum_should_match: 1 } };\n      }\n      return { bool: { must_not: clauses } };\n    }\n\n    if (value === \"*\") {","sourceCodeStart":500,"sourceCodeEnd":536,"githubUrl":"https://github.com/mem0ai/mem0/blob/001c235229be8795e3834520467bd0d661ed8f34/mem0-ts/src/oss/src/vector_stores/opensearch.ts#L500-L536","documentation":"When translating mem0 filters to OpenSearch bool queries, the logical combinators AND/OR/NOT must each hold an array of nested filter objects so they can be flat-mapped into sub-clauses. A non-array value (single object or scalar) cannot be expanded, so the builder throws naming the offending combinator key.","triggerScenarios":"Passing filters such as { AND: { user_id: 'a' } }, { OR: 'x' }, or { NOT: { run_id: 'y' } } to keywordSearch/vector search/list on the OpenSearch store.","commonSituations":"Filters authored for the Python SDK or Neptune backend using $and/$or/$not lowercase single objects and partially converted; dynamic filter assembly forgetting the array; JSON configs that drop single-element arrays.","solutions":["Wrap combinator values in arrays: { AND: [ { user_id: 'a' } ] }, { OR: [ ... ] }, { NOT: [ ... ] }.","If porting from $and/$or/$not style, also rename keys to uppercase AND/OR/NOT for this backend.","Centralize filter construction in one helper that always emits arrays for combinators."],"exampleFix":"// before\n{ NOT: { run_id: 'old' } }\n\n// after\n{ NOT: [ { run_id: 'old' } ] }","handlingStrategy":"validation","validationCode":"for (const k of ['AND', 'OR', 'NOT']) {\n  if (k in filters && !Array.isArray((filters as any)[k])) {\n    (filters as any)[k] = [(filters as any)[k]]; // normalize\n  }\n}","typeGuard":"const isClauseList = (v: unknown): v is Record<string, any>[] => Array.isArray(v) && v.every((e) => e && typeof e === 'object' && !Array.isArray(e));","tryCatchPattern":null,"preventionTips":["Use uppercase AND/OR/NOT with array values on OpenSearch","Convert $and/$or/$not to AND/OR/NOT when porting filters","Test filters against a tiny fixture before production"],"tags":["opensearch","filters","validation","typescript"],"backgroundTag":null,"analyzedSha":"001c235229be8795e3834520467bd0d661ed8f34","analyzedAt":"2026-08-15T01:55:42.685Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}