From 8e4662e930a5ae55a790ec3cc17ffaeec3758c7c Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Thu, 11 Jun 2026 20:20:36 -0700 Subject: [PATCH] fix(gbrain): stop forcing GBRAIN_PREPARE on transaction-mode poolers (#1965) buildGbrainEnv auto-set GBRAIN_PREPARE=true whenever DATABASE_URL targeted port 6543, and the /sync-gbrain capability check exported it for the rest of the skill run. Both had the semantics inverted: gbrain auto-disables prepared statements on transaction-mode poolers because they break every write there ("prepared statement does not exist"); GBRAIN_PREPARE=true is gbrain's documented override for SESSION-mode poolers on 6543, not a requirement for transaction mode. The #1435 search symptom the auto-set worked around was fixed gbrain-side. Remove both force-sets. A caller-set GBRAIN_PREPARE (either value) still passes through untouched, preserving the session-mode-on-6543 escape hatch. isTransactionModePooler stays exported. Co-Authored-By: Claude Fable 5 --- lib/gbrain-exec.ts | 31 +++++++++---------------------- sync-gbrain/SKILL.md | 9 +++++---- sync-gbrain/SKILL.md.tmpl | 9 +++++---- test/build-gbrain-env.test.ts | 17 +++++++++++------ 4 files changed, 30 insertions(+), 36 deletions(-) diff --git a/lib/gbrain-exec.ts b/lib/gbrain-exec.ts index 12855d11d..0cb1ddc57 100644 --- a/lib/gbrain-exec.ts +++ b/lib/gbrain-exec.ts @@ -58,10 +58,10 @@ export interface BuildGbrainEnvOptions { * Detect whether a DATABASE_URL targets a PgBouncer transaction-mode pooler. * * Supabase transaction-mode poolers conventionally run on port 6543 at - * `*.pooler.supabase.com`. When gbrain connects through one of these, it - * auto-disables prepared statements — but search requires them (#1435). - * Returns `true` when the URL looks like a transaction-mode pooler so the - * caller can set `GBRAIN_PREPARE=true` to re-enable prepared statements. + * `*.pooler.supabase.com`. gbrain auto-disables prepared statements on these + * (prepared statements break under transaction pooling — #1965); its banner + * documents `GBRAIN_PREPARE=true` as the override for poolers that actually + * run in session mode on 6543. */ export function isTransactionModePooler(url: string): boolean { try { @@ -83,10 +83,11 @@ export function isTransactionModePooler(url: string): boolean { * - the config has no `database_url`, * - the caller already set DATABASE_URL to the same value. * - * When the effective DATABASE_URL targets a PgBouncer transaction-mode - * pooler (port 6543), sets `GBRAIN_PREPARE=true` so gbrain re-enables - * prepared statements needed for search (#1435). Caller can override - * with `GBRAIN_PREPARE=false` in the base env. + * GBRAIN_PREPARE is never set here (#1965): gbrain auto-disables prepared + * statements on transaction-mode poolers itself, and forcing them on breaks + * every write with "prepared statement does not exist". A caller-set + * GBRAIN_PREPARE (either value) passes through untouched — that remains the + * documented override for session-mode poolers on port 6543. * * Always returns a fresh object — mutating the returned env never * affects the caller's env. Tests assert on effective values, not @@ -120,20 +121,6 @@ export function buildGbrainEnv(opts: BuildGbrainEnvOptions = {}): NodeJS.Process } } - // PgBouncer transaction-mode pooler detection (#1435): when the effective - // DATABASE_URL targets port 6543 (Supabase transaction-mode convention), - // gbrain auto-disables prepared statements — but search needs them. - // Set GBRAIN_PREPARE=true unless the caller explicitly opted out. - const effectiveUrl = out.DATABASE_URL || cfg.database_url; - if (effectiveUrl && !out.GBRAIN_PREPARE && isTransactionModePooler(effectiveUrl)) { - out.GBRAIN_PREPARE = "true"; - if (opts.announce) { - process.stderr.write( - `[gbrain-exec] set GBRAIN_PREPARE=true (port 6543 transaction-mode pooler detected)\n`, - ); - } - } - return out; } diff --git a/sync-gbrain/SKILL.md b/sync-gbrain/SKILL.md index a2abc2141..2098309ad 100644 --- a/sync-gbrain/SKILL.md +++ b/sync-gbrain/SKILL.md @@ -1013,10 +1013,11 @@ SLUG="_capability_check_$$" CAPABILITY_OK=0 if [ -f ~/.gbrain/config.json ] && \ gbrain --version 2>/dev/null | grep -q '^gbrain '; then - # GBRAIN_PREPARE=true ensures prepared statements stay enabled when - # connecting through a PgBouncer transaction-mode pooler (port 6543). - # Without it, search silently returns no results (#1435). - export GBRAIN_PREPARE=true + # Do NOT export GBRAIN_PREPARE here (#1965). gbrain auto-disables prepared + # statements on transaction-mode poolers (port 6543) — forcing them on + # breaks every write with "prepared statement does not exist". Users on a + # session-mode pooler at 6543 can set GBRAIN_PREPARE=true themselves (the + # gbrain banner documents this override). if echo "ping" | gbrain put "$SLUG" >/dev/null 2>&1; then # Retry search up to 3 times with 1s delay — under transaction-mode # pooling the search index may not be visible on the next connection diff --git a/sync-gbrain/SKILL.md.tmpl b/sync-gbrain/SKILL.md.tmpl index d63bd11a3..b6cc36816 100644 --- a/sync-gbrain/SKILL.md.tmpl +++ b/sync-gbrain/SKILL.md.tmpl @@ -301,10 +301,11 @@ SLUG="_capability_check_$$" CAPABILITY_OK=0 if [ -f ~/.gbrain/config.json ] && \ gbrain --version 2>/dev/null | grep -q '^gbrain '; then - # GBRAIN_PREPARE=true ensures prepared statements stay enabled when - # connecting through a PgBouncer transaction-mode pooler (port 6543). - # Without it, search silently returns no results (#1435). - export GBRAIN_PREPARE=true + # Do NOT export GBRAIN_PREPARE here (#1965). gbrain auto-disables prepared + # statements on transaction-mode poolers (port 6543) — forcing them on + # breaks every write with "prepared statement does not exist". Users on a + # session-mode pooler at 6543 can set GBRAIN_PREPARE=true themselves (the + # gbrain banner documents this override). if echo "ping" | gbrain put "$SLUG" >/dev/null 2>&1; then # Retry search up to 3 times with 1s delay — under transaction-mode # pooling the search index may not be visible on the next connection diff --git a/test/build-gbrain-env.test.ts b/test/build-gbrain-env.test.ts index be7b8f54d..46403ba41 100644 --- a/test/build-gbrain-env.test.ts +++ b/test/build-gbrain-env.test.ts @@ -118,15 +118,20 @@ describe("buildGbrainEnv", () => { expect(result.DATABASE_URL).toBe("postgresql://gbrain/db"); }); - // --- GBRAIN_PREPARE auto-detection (#1435) --- + // --- GBRAIN_PREPARE is never auto-set (#1965) --- + // gbrain auto-disables prepared statements on transaction-mode poolers; + // forcing GBRAIN_PREPARE=true there breaks every write with "prepared + // statement does not exist". The helper must leave the variable alone in + // all cases — a caller-set value (the documented session-mode-on-6543 + // override) passes through untouched. - it("sets GBRAIN_PREPARE=true when DATABASE_URL targets port 6543 (transaction-mode pooler)", () => { + it("does not set GBRAIN_PREPARE when DATABASE_URL targets port 6543 (transaction-mode pooler)", () => { const poolerUrl = "postgresql://postgres.abc:pw@aws-0-us-east-1.pooler.supabase.com:6543/postgres"; writeFileSync(join(gbrainHome, "config.json"), JSON.stringify({ database_url: poolerUrl })); const baseEnv = { HOME: home }; const result = buildGbrainEnv({ baseEnv }); expect(result.DATABASE_URL).toBe(poolerUrl); - expect(result.GBRAIN_PREPARE).toBe("true"); + expect(result.GBRAIN_PREPARE).toBeUndefined(); }); it("does not set GBRAIN_PREPARE when DATABASE_URL targets port 5432 (session-mode pooler)", () => { @@ -144,7 +149,7 @@ describe("buildGbrainEnv", () => { expect(result.GBRAIN_PREPARE).toBeUndefined(); }); - it("respects caller's explicit GBRAIN_PREPARE=false (opt-out)", () => { + it("passes through caller's explicit GBRAIN_PREPARE=false", () => { const poolerUrl = "postgresql://postgres.abc:pw@aws-0-us-east-1.pooler.supabase.com:6543/postgres"; writeFileSync(join(gbrainHome, "config.json"), JSON.stringify({ database_url: poolerUrl })); const baseEnv = { HOME: home, GBRAIN_PREPARE: "false" }; @@ -152,10 +157,10 @@ describe("buildGbrainEnv", () => { expect(result.GBRAIN_PREPARE).toBe("false"); }); - it("sets GBRAIN_PREPARE even when caller DATABASE_URL already matches config on port 6543", () => { + it("passes through caller's explicit GBRAIN_PREPARE=true (session-mode-on-6543 override)", () => { const poolerUrl = "postgresql://postgres.abc:pw@aws-0-us-east-1.pooler.supabase.com:6543/postgres"; writeFileSync(join(gbrainHome, "config.json"), JSON.stringify({ database_url: poolerUrl })); - const baseEnv = { HOME: home, DATABASE_URL: poolerUrl }; + const baseEnv = { HOME: home, GBRAIN_PREPARE: "true" }; const result = buildGbrainEnv({ baseEnv }); expect(result.GBRAIN_PREPARE).toBe("true"); });