From 30194463b5c59a71dc2a47f72008f74218d35cf3 Mon Sep 17 00:00:00 2001 From: John Knapp Date: Fri, 12 Jun 2026 11:38:13 -0700 Subject: [PATCH] fix: bind memory.list tag filter as a single text[] param (#1) memory.list built its tag filter with a raw sql template: sql`${memories.tags} @> ${tags}::text[]` Drizzle expands a JS array embedded in a sql template into positional params, so one tag produced `@> ($1)::text[]` (Postgres rejected the bound string as a malformed array literal) and two tags produced `@> ($1,$2)::text[]` (a record, hence "cannot cast type record to text[]"). Switch to arrayContains(memories.tags, tags), which binds the array as one text[] param via the column's toDriver and preserves the "require ALL tags" (@>) semantics. Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/web/lib/mcp/tools.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/web/lib/mcp/tools.ts b/apps/web/lib/mcp/tools.ts index 0ec4e00..6e4e8ac 100644 --- a/apps/web/lib/mcp/tools.ts +++ b/apps/web/lib/mcp/tools.ts @@ -1,4 +1,4 @@ -import { and, desc, eq, inArray, isNull, or, sql } from "drizzle-orm"; +import { and, arrayContains, desc, eq, inArray, isNull, or } from "drizzle-orm"; import { db } from "@/lib/db/client"; import { memories, @@ -465,7 +465,13 @@ const memoryList: ToolDef = { } if (parsed.data.tags && parsed.data.tags.length > 0) { - where.push(sql`${memories.tags} @> ${parsed.data.tags}::text[]`); + // Require ALL listed tags (array containment). Use Drizzle's + // arrayContains so the JS array binds as a single text[] param + // (via the column's toDriver) rather than being expanded into + // positional params — a raw `${tags}::text[]` template expands to + // `($1)::text[]` / `($1,$2)::text[]`, which Postgres rejects as a + // malformed array literal / record cast. + where.push(arrayContains(memories.tags, parsed.data.tags)); } const rows = await db