diff --git a/CHANGELOG.md b/CHANGELOG.md index 11365ae68..ef8b61735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ All notable changes to GBrain will be documented in this file. +## [0.42.26.0] - 2026-06-04 + +**The Supabase setup docs now match the current dashboard and call out the one thing that actually breaks on IPv4 hosts.** The connection-string instructions were written for the old Supabase UI (two options under Project Settings) and used inconsistent pooler names: some docs said "Connection pooler," others mislabeled port 6543 as the "Session pooler," and a few carried a stale warning to avoid the transaction pooler entirely. The current Supabase UI puts the string under **Connect** in the top navigation with three options (Direct, Transaction pooler, Session pooler). gbrain is tuned for the **Transaction pooler** (port 6543): it disables prepared statements there and routes migrations, DDL, and worker locks to a separate direct connection. This release makes every setup surface say that, consistently. + +It also documents the IPv4 footgun that was easy to hit and hard to diagnose: the direct connection gbrain derives for migrations and locks is IPv6-only, so on an IPv4-only host (most Render plans) reads work but sync silently skips pages. The tutorial now leads with the free fix (point `GBRAIN_DIRECT_DATABASE_URL` at the Session pooler, port 5432, IPv4) and keeps the IPv4 add-on as the paid alternative. + +Docs only, nothing to configure. Thanks to @FilipHarald (#1848) for catching the outdated UI walkthrough. + +### Changed +- Supabase tutorial (`docs/tutorials/personal-brain.md`), `gbrain init` prompts, the setup skill, the verify runbook, and the live-sync guide all recommend the **Transaction pooler** (port 6543) via the new **Connect** navigation, and explain the IPv4 direct-connection fix. + +### Fixed +- Removed stale "transaction mode pooler breaks sync (`.begin() is not a function`)" warnings that contradicted gbrain's current dual-pool behavior, plus the mislabeling of port 6543 as the Session pooler. + +### To take advantage of v0.42.26.0 + +Nothing to run. If you set up a Supabase brain on an IPv4-only host and `gbrain stats` shows far fewer pages than files, point the direct connection at the Session pooler: + +```bash +export GBRAIN_DIRECT_DATABASE_URL="postgresql://postgres.YOUR-PROJECT:YOUR-PASSWORD@aws-0-YOUR-REGION.pooler.supabase.com:5432/postgres" +gbrain sync --full +``` + ## [0.42.25.0] - 2026-06-03 **Cost caps and budget gates now fire on Opus 4.8 — and every model price lives in one place, so they can't silently drift again.** If you pointed a gbrain tier at Opus 4.8 (`models.aliases.opus`), the cost guardrails were quietly not working: there was no price on file for 4.8, so the `gbrain dream` budget meter let runs proceed unbounded (it warns `BUDGET_METER_NO_PRICING` and skips the gate for unpriced models), and `gbrain skillopt --max-cost-usd` fell back to a cheaper tier's rate and refused too late. This release adds Opus 4.8 pricing ($5 in / $25 out per 1M tokens, same as 4.7) and the caps enforce again. diff --git a/VERSION b/VERSION index 44cf481ac..b8bc4029e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.42.25.0 +0.42.26.0 \ No newline at end of file diff --git a/docs/GBRAIN_VERIFY.md b/docs/GBRAIN_VERIFY.md index 5246924bc..5bda19125 100644 --- a/docs/GBRAIN_VERIFY.md +++ b/docs/GBRAIN_VERIFY.md @@ -88,14 +88,15 @@ find /data/brain -name '*.md' \ Some difference is normal (files added since last sync), but if page count is less than half the file count, sync is silently skipping pages. -**If page count is way too low:** The #1 cause is the connection pooler bug. -Check your `DATABASE_URL`: -- If it contains `pooler.supabase.com:6543`, verify it's using **Session mode**, - not Transaction mode. -- Transaction mode breaks `engine.transaction()` and causes `.begin() is not a - function` errors. -- Fix: switch to Session mode pooler string, then run `gbrain sync --full` - to reimport everything. +**If page count is way too low:** The #1 cause is an unreachable direct +connection on an IPv4-only host. GBrain uses the Transaction pooler (port 6543) +for reads, but routes migrations, DDL, and sync transactions to a derived direct +connection (`db..supabase.co:5432`), which is IPv6-only. +- On an IPv4-only host, reads work but sync transactions fail and silently skip + pages. +- Fix: set `GBRAIN_DIRECT_DATABASE_URL` to the **Session pooler** string (port + 5432 on the `pooler.supabase.com` host, IPv4), or enable Supabase's IPv4 + add-on. Then run `gbrain sync --full` to reimport everything. ### 4b. Embed Check @@ -142,7 +143,8 @@ gbrain search "" - Is `gbrain sync --watch` still alive (if using watch mode)? - Run `gbrain config get sync.last_run` to see when sync last ran. - Run `gbrain sync --repo /data/brain` manually and check for errors. -- If you see `.begin() is not a function`, fix the pooler (see 4a above). +- If sync errors mention an unreachable host or connection timeout, the direct + connection isn't reachable on IPv4 (see 4a above). --- diff --git a/docs/guides/live-sync.md b/docs/guides/live-sync.md index cacd8fe76..15f8f5f6e 100644 --- a/docs/guides/live-sync.md +++ b/docs/guides/live-sync.md @@ -15,17 +15,20 @@ with the brain repo automatically. You never have to remember to run sync. ## Implementation -### Prerequisite: Session Mode Pooler +### Prerequisite: a reachable direct connection -Sync uses `engine.transaction()` on every import. If `DATABASE_URL` points to -Supabase's **Transaction mode** pooler, sync will throw `.begin() is not a -function` and **silently skip most pages**. This is the number one cause of -"sync ran but nothing happened." +GBrain is tuned for the Supabase **Transaction pooler** (port 6543): it +auto-disables prepared statements there and routes `engine.transaction()` +(migrations, DDL, sync imports) to a derived **direct** connection +(`db..supabase.co:5432`). That direct host is IPv6-only, so on an +IPv4-only host, reads work but sync **silently skips most pages**. This is the +number one cause of "sync ran but nothing happened." -Fix: use the **Session mode** pooler string (port 6543, Session mode) or the -direct connection (port 5432, IPv6-only). Verify by running `gbrain sync` and -checking that the page count in `gbrain stats` matches the syncable file count -in the repo. +Fix: make the direct connection reachable over IPv4. Either set +`GBRAIN_DIRECT_DATABASE_URL` to the **Session pooler** string (port 5432 on the +`pooler.supabase.com` host, IPv4), or enable Supabase's IPv4 add-on. Verify by +running `gbrain sync` and checking that the page count in `gbrain stats` matches +the syncable file count in the repo. ### The Primitives @@ -58,8 +61,9 @@ gbrain sync --repo /data/brain && gbrain embed --stale Name: gbrain-auto-sync Schedule: */15 * * * * Prompt: "Run: gbrain sync --repo /data/brain && gbrain embed --stale - Log the result. If sync fails with .begin() is not a function, - the DATABASE_URL is using Transaction mode pooler." + Log the result. If sync errors mention an unreachable host or timeout, + the direct connection isn't reachable over IPv4 (set + GBRAIN_DIRECT_DATABASE_URL to the Session pooler, or enable the IPv4 add-on)." ``` **Hermes:** @@ -125,8 +129,8 @@ hashes match. If both a cron and `--watch` fire simultaneously, no conflict. 2. **Compare page count to file count.** Run `gbrain stats` and count the syncable markdown files in the brain repo. The page count in the database - should match. If they diverge, files are being silently skipped (likely - a Transaction mode pooler issue). + should match. If they diverge, files are being silently skipped (likely an + unreachable direct connection on IPv4 — see the prerequisite above). 3. **Check embedded chunk count.** In `gbrain stats`, the embedded chunk count should be close to the total chunk count. A large gap means diff --git a/docs/tutorials/personal-brain.md b/docs/tutorials/personal-brain.md index 2bb270c0d..3be55c8f2 100644 --- a/docs/tutorials/personal-brain.md +++ b/docs/tutorials/personal-brain.md @@ -145,14 +145,15 @@ GBrain uses Supabase for vector embeddings and full-text search at scale. There Skip this and every embed write fails with "type vector does not exist" the moment GBrain tries to create its schema. pgvector is what stores the embeddings; the schema migrations refuse to run without it. Five seconds in the UI; an hour of debugging if you forget. -### 7b. Get the CONNECTION POOLER connection string, not the direct one +### 7b. Get the TRANSACTION POOLER connection string, not the direct one -In **Project Settings → Database → Connection string**, Supabase shows you two options. They look almost identical. Use the right one. +In the Supabase dashboard, click **Connect** in the top navigation bar, then **Connection String**. Supabase shows three options. They look almost identical. Use the right one. -- **Direct connection** (port 5432). Talks straight to the Postgres instance. IPv6-only. Will fail if your Render host doesn't have IPv6 outbound (most don't by default). -- **Connection pooler** (port 6543, hostname starts with `aws-0-...pooler.supabase.com`). Talks through Supabase's pgbouncer. Works over IPv4. Survives connection storms from parallel workers. +- **Direct connection** (port 5432, host `db.YOUR-PROJECT.supabase.co`). Talks straight to the Postgres instance. IPv6-only. Will fail if your Render host doesn't have IPv6 outbound (most don't by default). +- **Transaction pooler** (port 6543, host `aws-0-...pooler.supabase.com`). Talks through Supabase's pooler (Supavisor) in transaction mode. Works over IPv4. Survives connection storms from parallel workers. GBrain is tuned for this one: it auto-disables prepared statements on port 6543 and routes migrations, DDL, and worker locks to a separate direct connection (see 7c). +- **Session pooler** (port 5432, host `aws-0-...pooler.supabase.com`). Also works over IPv4, with full session features. You don't need it as your main URL, but it's the free way to fix the IPv4 gotcha in 7c. -You want the **connection pooler** string. Format looks like: +You want the **Transaction pooler** string. Format looks like: ``` postgresql://postgres.YOUR-PROJECT:YOUR-PASSWORD@aws-0-us-west-1.pooler.supabase.com:6543/postgres @@ -164,11 +165,23 @@ Configure it via: gbrain config set database_url "postgresql://postgres.YOUR-PROJECT:YOUR-PASSWORD@aws-0-us-west-1.pooler.supabase.com:6543/postgres" ``` -### 7c. Buy the IPv4 add-on if your host is IPv4-only +### 7c. Fix the IPv4 gotcha for migrations, DDL, and worker locks -Even with the pooler, some Supabase regions and some Render plans hit IPv6 resolution snags. If your `gbrain doctor` shows connection failures and the error mentions "network unreachable" or hangs forever on connect, you need Supabase's **IPv4 add-on**. +The transaction pooler (7b) carries your normal reads and writes over IPv4. But GBrain runs schema migrations, DDL, and background-worker locks on a *direct* connection, which it derives from your pooler URL by swapping the host to `db.YOUR-PROJECT.supabase.co:5432`. That direct host is **IPv6-only**. On an IPv4-only host (most Render plans), reads work but migrations hang and worker locks orphan, often silently. -In the Supabase dashboard, **Project Settings → Add-ons → IPv4 address**. About $4 a month. Toggle on, wait a minute, retry the connection. This bit me on multiple installs before I learned to just buy it up front. +Two ways to fix it. The free one first: + +**Free: point GBrain's direct connection at the Session pooler.** The session pooler is the same Supavisor host on port 5432, and it's IPv4. Copy the **Session pooler** string from the same **Connect → Connection String** panel and set it as the direct-connection override: + +```bash +export GBRAIN_DIRECT_DATABASE_URL="postgresql://postgres.YOUR-PROJECT:YOUR-PASSWORD@aws-0-us-west-1.pooler.supabase.com:5432/postgres" +``` + +Now both pools — reads on the transaction pooler (6543), DDL and locks on the session pooler (5432) — run over IPv4 at zero extra cost. + +**Paid: buy Supabase's IPv4 add-on.** About $4 a month, Pro tier or higher. It makes the direct `db.*.supabase.co` host reachable over IPv4, so the derived direct connection just works with no extra config. In the Supabase dashboard, **Project Settings → Add-ons → IPv4 address**. Toggle on, wait a minute, retry. + +Either fixes it. If `gbrain doctor` still shows connection failures that mention "network unreachable" or hangs forever on connect, you haven't done one of these yet. ### 7d. Verify the connection diff --git a/llms-full.txt b/llms-full.txt index e90fef474..495e33721 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -2511,17 +2511,20 @@ with the brain repo automatically. You never have to remember to run sync. ## Implementation -### Prerequisite: Session Mode Pooler +### Prerequisite: a reachable direct connection -Sync uses `engine.transaction()` on every import. If `DATABASE_URL` points to -Supabase's **Transaction mode** pooler, sync will throw `.begin() is not a -function` and **silently skip most pages**. This is the number one cause of -"sync ran but nothing happened." +GBrain is tuned for the Supabase **Transaction pooler** (port 6543): it +auto-disables prepared statements there and routes `engine.transaction()` +(migrations, DDL, sync imports) to a derived **direct** connection +(`db..supabase.co:5432`). That direct host is IPv6-only, so on an +IPv4-only host, reads work but sync **silently skips most pages**. This is the +number one cause of "sync ran but nothing happened." -Fix: use the **Session mode** pooler string (port 6543, Session mode) or the -direct connection (port 5432, IPv6-only). Verify by running `gbrain sync` and -checking that the page count in `gbrain stats` matches the syncable file count -in the repo. +Fix: make the direct connection reachable over IPv4. Either set +`GBRAIN_DIRECT_DATABASE_URL` to the **Session pooler** string (port 5432 on the +`pooler.supabase.com` host, IPv4), or enable Supabase's IPv4 add-on. Verify by +running `gbrain sync` and checking that the page count in `gbrain stats` matches +the syncable file count in the repo. ### The Primitives @@ -2554,8 +2557,9 @@ gbrain sync --repo /data/brain && gbrain embed --stale Name: gbrain-auto-sync Schedule: */15 * * * * Prompt: "Run: gbrain sync --repo /data/brain && gbrain embed --stale - Log the result. If sync fails with .begin() is not a function, - the DATABASE_URL is using Transaction mode pooler." + Log the result. If sync errors mention an unreachable host or timeout, + the direct connection isn't reachable over IPv4 (set + GBRAIN_DIRECT_DATABASE_URL to the Session pooler, or enable the IPv4 add-on)." ``` **Hermes:** @@ -2621,8 +2625,8 @@ hashes match. If both a cron and `--watch` fire simultaneously, no conflict. 2. **Compare page count to file count.** Run `gbrain stats` and count the syncable markdown files in the brain repo. The page count in the database - should match. If they diverge, files are being silently skipped (likely - a Transaction mode pooler issue). + should match. If they diverge, files are being silently skipped (likely an + unreachable direct connection on IPv4 — see the prerequisite above). 3. **Check embedded chunk count.** In `gbrain stats`, the embedded chunk count should be close to the total chunk count. A large gap means @@ -3713,14 +3717,15 @@ find /data/brain -name '*.md' \ Some difference is normal (files added since last sync), but if page count is less than half the file count, sync is silently skipping pages. -**If page count is way too low:** The #1 cause is the connection pooler bug. -Check your `DATABASE_URL`: -- If it contains `pooler.supabase.com:6543`, verify it's using **Session mode**, - not Transaction mode. -- Transaction mode breaks `engine.transaction()` and causes `.begin() is not a - function` errors. -- Fix: switch to Session mode pooler string, then run `gbrain sync --full` - to reimport everything. +**If page count is way too low:** The #1 cause is an unreachable direct +connection on an IPv4-only host. GBrain uses the Transaction pooler (port 6543) +for reads, but routes migrations, DDL, and sync transactions to a derived direct +connection (`db..supabase.co:5432`), which is IPv6-only. +- On an IPv4-only host, reads work but sync transactions fail and silently skip + pages. +- Fix: set `GBRAIN_DIRECT_DATABASE_URL` to the **Session pooler** string (port + 5432 on the `pooler.supabase.com` host, IPv4), or enable Supabase's IPv4 + add-on. Then run `gbrain sync --full` to reimport everything. ### 4b. Embed Check @@ -3767,7 +3772,8 @@ gbrain search "" - Is `gbrain sync --watch` still alive (if using watch mode)? - Run `gbrain config get sync.last_run` to see when sync last ran. - Run `gbrain sync --repo /data/brain` manually and check for errors. -- If you see `.begin() is not a function`, fix the pooler (see 4a above). +- If sync errors mention an unreachable host or connection timeout, the direct + connection isn't reachable on IPv4 (see 4a above). --- diff --git a/package.json b/package.json index 19034af85..d438ec6f2 100644 --- a/package.json +++ b/package.json @@ -143,5 +143,5 @@ "bun": ">=1.3.10" }, "license": "MIT", - "version": "0.42.25.0" + "version": "0.42.26.0" } diff --git a/skills/setup/SKILL.md b/skills/setup/SKILL.md index fbe787036..d88f2e532 100644 --- a/skills/setup/SKILL.md +++ b/skills/setup/SKILL.md @@ -37,11 +37,10 @@ GBrain connects directly to Postgres over the wire protocol. NOT through the Supabase REST API. You need the **database connection string** (a `postgresql://` URI), not the project URL or anon key. The password is embedded in the connection string. -Use the **Shared Pooler** connection string (port 6543), not the direct connection -(port 5432). The direct hostname resolves to IPv6 only, which many environments -can't reach. Find it: go to the project, click **Get Connected** next to the -project URL, then **Direct Connection String** > **Session Pooler**, and copy -the **Shared Pooler** connection string. +Use the **Transaction pooler** connection string (port 6543), not the direct +connection (port 5432). The direct hostname resolves to IPv6 only, which many +environments can't reach. Find it: click **Connect** in the top navigation bar, +then **Connection String** > **Transaction pooler**, and copy the string. **Do NOT ask for the Supabase anon key.** GBrain doesn't use it. @@ -171,9 +170,9 @@ Guide the user through creating a Supabase project: - Region: pick the one closest to you - Database password: generate a strong one and save it 3. "Wait about 2 minutes for the project to initialize." -4. "Find the connection string: go to your project, click **Get Connected** next - to the project URL, then **Direct Connection String** > **Session Pooler**, - and copy the **Shared Pooler** connection string (port 6543)." +4. "Find the connection string: click **Connect** in the top navigation bar, + then **Connection String** > **Transaction pooler**, and copy the string + (port 6543)." 5. Initialize gbrain: ```bash gbrain init --non-interactive --url "postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres" @@ -194,7 +193,7 @@ If the user already has Postgres with pgvector: If the connection fails with ECONNREFUSED and the URL contains `supabase.co`, the user probably pasted the direct connection (IPv6 only). Guide them to the -Session pooler string instead (see Phase A step 4). +Transaction pooler string instead (see Phase A step 4). ## Phase C: First Import @@ -412,7 +411,7 @@ output. It checks connection, pgvector, RLS, schema version, and embeddings. | What You See | Why | Fix | |---|---|---| -| Connection refused | Supabase project paused, IPv6, or wrong URL | Use Session pooler (port 6543), or supabase.com/dashboard > Restore | +| Connection refused | Supabase project paused, IPv6, or wrong URL | Use Transaction pooler (port 6543), or supabase.com/dashboard > Restore | | Password authentication failed | Wrong password | Project Settings > Database > Reset password | | pgvector not available | Extension not enabled | Run `CREATE EXTENSION vector;` in SQL Editor | | OpenAI key invalid | Expired or wrong key | platform.openai.com/api-keys > Create new | @@ -441,10 +440,14 @@ vector DB falls behind and gbrain returns stale answers. This phase is not optio Read `docs/GBRAIN_SKILLPACK.md` Section 18 for the full reference. Key points: -1. **Check the connection pooler first.** Sync uses transactions on every import. - If `DATABASE_URL` uses Supabase's Transaction mode pooler, sync will throw - `.begin() is not a function` and silently skip most pages. Verify the connection - string uses Session mode (port 6543, Session mode) or direct (port 5432). +1. **Check the connection first.** GBrain is tuned for the Supabase **Transaction + pooler** (port 6543): it auto-disables prepared statements there and routes + migrations, DDL, and sync transactions to a separate direct connection. That + derived direct connection (`db..supabase.co:5432`) is IPv6-only, so on an + IPv4-only host, reads work but sync silently skips pages. Fix by making the + direct connection reachable: set `GBRAIN_DIRECT_DATABASE_URL` to the **Session + pooler** string (port 5432 on the `pooler.supabase.com` host, IPv4), or enable + Supabase's IPv4 add-on. 2. **Set up automatic sync.** Choose the approach that fits your environment: - **Cron** (recommended for agents): register a cron every 5-30 minutes: @@ -456,7 +459,8 @@ Read `docs/GBRAIN_SKILLPACK.md` Section 18 for the full reference. Key points: 3. **Verify sync works.** Don't just check that the command ran. Check that it worked: - `gbrain stats` should show page count close to syncable file count in the repo. - - If page count is way too low, the pooler bug is silently skipping pages. + - If page count is way too low, the direct connection is unreachable on IPv4 and + sync is silently skipping pages (see point 1). - Push a test change and confirm it appears in `gbrain search`. 4. **Chain sync + embed.** Always run both: `gbrain sync --repo && gbrain @@ -535,7 +539,7 @@ re-suggesting things the user already declined. - **Asking for the Supabase anon key.** GBrain connects directly to Postgres over the wire protocol, not through the REST API. Only the database connection string is needed. - **Skipping live sync setup.** If sync doesn't run automatically, the vector DB falls behind and search returns stale answers. Phase H is not optional. - **Declaring setup complete without verification.** "The command ran" is not the same as "it worked." Push a test change, wait for sync, search for the corrected text. -- **Using Transaction mode pooler.** Sync uses transactions on every import. Transaction mode pooler causes `.begin() is not a function` errors and silently skips pages. Always use Session mode (port 6543). +- **Leaving the direct connection unreachable on IPv4.** GBrain uses the Transaction pooler (port 6543) for reads and a derived direct connection (`db..supabase.co:5432`, IPv6-only) for migrations, DDL, and sync transactions. On an IPv4-only host, reads work but sync silently skips pages. Set `GBRAIN_DIRECT_DATABASE_URL` to the Session pooler string (port 5432, IPv4), or enable the IPv4 add-on. - **Importing without proving search.** The magical moment is the user seeing search find things grep couldn't. Don't skip it. ## Output Format diff --git a/src/commands/init.ts b/src/commands/init.ts index 8ca51b522..3f7dbd636 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -1072,9 +1072,8 @@ async function initPostgres(opts: { console.warn(''); console.warn('WARNING: You provided a Supabase direct connection URL (db.*.supabase.co:5432).'); console.warn(' Direct connections are IPv6 only and fail in many environments.'); - console.warn(' Use the Session pooler connection string instead (port 6543):'); - console.warn(' Supabase Dashboard > gear icon (Project Settings) > Database >'); - console.warn(' Connection string > URI tab > change dropdown to "Session pooler"'); + console.warn(' Use the Transaction pooler connection string instead (port 6543):'); + console.warn(' Supabase Dashboard > Connect (top bar) > Connection String > Transaction pooler'); console.warn(''); } @@ -1087,7 +1086,7 @@ async function initPostgres(opts: { const msg = e instanceof Error ? e.message : String(e); if (databaseUrl.includes('supabase.co') && (msg.includes('ECONNREFUSED') || msg.includes('ETIMEDOUT'))) { console.error('Connection failed. Supabase direct connections (db.*.supabase.co:5432) are IPv6 only.'); - console.error('Use the Session pooler connection string instead (port 6543).'); + console.error('Use the Transaction pooler connection string instead (port 6543).'); } throw e; } @@ -1277,7 +1276,7 @@ async function supabaseWizard(): Promise { console.log('\nEnter your Supabase/Postgres connection URL:'); console.log(' Format: postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres'); /* allow-pg-url-literal */ - console.log(' Find it: Supabase Dashboard > Connect (top bar) > Connection String > Session Pooler\n'); + console.log(' Find it: Supabase Dashboard > Connect (top bar) > Connection String > Transaction pooler\n'); const url = await readLine('Connection URL: '); if (!url) {