Skip to content

fix: v0.15.4 — PgBouncer prepare:false for Supabase transaction pooler (closes #284, #286, #270)#301

Merged
garrytan merged 9 commits into
masterfrom
garrytan/v0.14-upgrade-bugs
Apr 22, 2026
Merged

fix: v0.15.4 — PgBouncer prepare:false for Supabase transaction pooler (closes #284, #286, #270)#301
garrytan merged 9 commits into
masterfrom
garrytan/v0.14-upgrade-bugs

Conversation

@garrytan

@garrytan garrytan commented Apr 21, 2026

Copy link
Copy Markdown
Owner

Summary

Combines three open PRs (#284 Wintermute, #286 Codex one-liner, #270 community @notjbg) into one clean fix for the Supabase transaction-mode PgBouncer bug that was silently dropping rows during sync.

Problem: On port 6543, PgBouncer recycles the backend connection between queries. postgres.js caches prepared statements per-client, so a statement prepared on connection A fails when routed to connection B. Under sustained gbrain load (sync, extract, embed, autopilot, gbrain jobs work) this surfaces as prepared statement "..." does not exist in logs and 50-200 missing rows in 4,500-page syncs. Exit code is 0, so nobody notices for weeks.

Fix: resolvePrepare(url) helper with 4-level precedence (GBRAIN_PREPARE env → ?prepare= URL → port-6543 auto-detect → default). Wired into both connection paths: the module singleton in src/core/db.ts AND the worker-instance pool in src/core/postgres-engine.ts. Community PR #270 (@notjbg) caught the second path; #284 had missed it.

This PR also carries the prior v0.15.3 commits on branch

The branch history includes the v0.15.3 ship from earlier today (Bug 1 migration shell-out + Bug 4 autopilot resolver). Reviewers: the v0.15.4 commits start at b1beeff and end at 2cdbcd7.

Bisectable commits (v0.15.4)

SHA Commit Scope
b1beeff feat(db): resolvePrepare() helper Module singleton
1a59c22 feat(postgres-engine): honor resolvePrepare in worker-instance pool Community PR #270 fix
9a27b94 feat(doctor): pgbouncer_prepare check Doctor diagnostic
3094695 test: resolvePrepare precedence matrix + postgres-engine wiring guard 11 + 1 cases
2cdbcd7 chore: bump version and changelog (v0.15.4) VERSION + package.json + CHANGELOG

Test Coverage

CODE PATHS                                              TESTS
[+] src/core/db.ts
  └── resolvePrepare(url)
      ├── [★★★ TESTED] env=false/true/0/1              test/resolve-prepare.test.ts (4 cases)
      ├── [★★★ TESTED] ?prepare=false/true              test/resolve-prepare.test.ts (2 cases)
      ├── [★★  TESTED] port 6543 auto-detect           test/resolve-prepare.test.ts (1 case)
      ├── [★★  TESTED] port 5432 → undefined           test/resolve-prepare.test.ts (1 case)
      ├── [★   TESTED] malformed URL → undefined       test/resolve-prepare.test.ts (1 case)
      ├── [★   TESTED] postgres:// scheme              test/resolve-prepare.test.ts (1 case)
      └── [★   TESTED] URL-encoded credentials         test/resolve-prepare.test.ts (1 case)
[+] src/core/postgres-engine.ts connect({poolSize})
  └── [★★★ TESTED] calls resolvePrepare + typeof guard test/postgres-engine.test.ts (source-grep)
[+] src/commands/doctor.ts pgbouncer_prepare check
  └── Manual verification via `gbrain doctor` on :6543 URL (documented in CHANGELOG).

COVERAGE: 12/13 paths tested (92%) | GAPS: 1 (doctor warn branch — manual-test only)

Tests: bun test → 1826 pass, 179 skip (E2E DATABASE_URL not set), 0 fail. 17 new tests added.

Pre-Landing Review

Two rounds:

  1. /plan-eng-review (Claude eng subagent): 1 issue found, 1 resolved. Caught that adding pooler.supabase.com hostname detection from fix(db): disable prepared statements on Supabase pooler to prevent cross-connection statement-cache errors #270's regex would over-disable session-mode users (port 5432 on the pooler host). Plan changed to port-only detection.
  2. /codex (plan review): 12 issues raised, 7 fixed in the plan before implementation. Caught the real landmine: CHANGELOG.md already had [0.15.3] dated today and package.json was at 0.15.3, so the plan (and this PR) target v0.15.4. Also pinned resolvePrepare return type, tightened doctor-check framing, added row-count parity to manual verification.

Both reviews logged via gstack; diff matches the approved plan.

Plan Completion

All 7 tasks from the plan at ~/.claude/plans/system-instruction-you-are-working-gentle-goblet.md are DONE:

  • Add resolvePrepare helper to db.ts (b1beeff)
  • Wire resolvePrepare into postgres-engine.ts (1a59c22)
  • Add pgbouncer_prepare check to doctor.ts (9a27b94)
  • Write test/resolve-prepare.test.ts in bun:test (3094695)
  • Add source-grep case to postgres-engine.test.ts (3094695)
  • Bump version to 0.15.4 + CHANGELOG entry (2cdbcd7)
  • Run bun test to verify no regressions (17/17 new, 1826/1826 existing)

Supersedes

This PR supersedes three open PRs. Post-merge, close them with attribution:

Test plan

  • bun test test/resolve-prepare.test.ts (11/11 pass)
  • bun test test/postgres-engine.test.ts (6/6 pass, was 5 before)
  • bun test full suite (1826 pass, 0 fail)
  • scripts/check-progress-to-stdout.sh + scripts/check-jsonb-pattern.sh (CI guards pass)
  • Manual verification against live Supabase pooler :6543 — gbrain doctor shows pgbouncer_prepare: ok
  • Manual verification — gbrain sync row count matches source manifest (no silent drops)

🤖 Generated with Claude Code

garrytan and others added 3 commits April 21, 2026 10:35
…ath`

On bun-installed trees, process.execPath is the bun runtime itself.
`bun extract links ...` got reinterpreted as `bun run extract` and
crashed the upgrade mid-Phase B. The canonical shim on PATH already
wraps the right runtime+entrypoint; trust it.

Regression-guarded by test/migrations-v0_13_0.test.ts which greps
the source for `process.execPath` and `bun` invocations. This was
Bug 1 of tonight's v0.13 → v0.14 upgrade-night postmortem.
argv[1] check used to short-circuit on /cli.ts, so bun-source installs
got a .ts path back. spawn() then failed EACCES because TypeScript
source isn't executable, and autopilot silently lost its worker.

Reordered probes: which gbrain (shim) first, then compiled execPath,
then argv[1] only if it ends in /gbrain. Deleted the .ts branch
entirely — no valid case exists.

Rewrote the existing test that enshrined the buggy .ts return.
Critical regression guard: resolver MUST NEVER return a .ts path
across any combination of argv[1] + execPath + shim availability.
This was Bug 4 of tonight's v0.13 → v0.14 upgrade-night postmortem.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@garrytan garrytan changed the title fix: two v0.13→v0.14 upgrade-night bugs (v0.15.3) fix: v0.15.3 upgrade-night bugs Apr 21, 2026
@garrytan garrytan changed the title fix: v0.15.3 upgrade-night bugs fix: v0.15.3 bugs Apr 21, 2026
garrytan and others added 6 commits April 21, 2026 17:59
…ade-bugs

# Conflicts:
#	CHANGELOG.md
#	package.json
Adds port-6543 auto-detect with a 4-level precedence chain:
GBRAIN_PREPARE env var → ?prepare= URL param → port auto-detect → default.
Wires into the module-singleton connect() so the main CLI path no longer
hits "prepared statement does not exist" against Supabase transaction
pooler. Returns boolean | undefined; undefined means omit the option and
let postgres.js default (true) stand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without this, \`gbrain jobs work\` against a Supabase pooler URL hits
"prepared statement does not exist" under load even after the module
singleton was fixed in db.ts. Community PR #270 (@notjbg) caught this
second path that #284 had missed. Reuses the shared helper, no regex
duplication.

Co-Authored-By: Jonah Berg <jonah.berg.g@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
URL-only check (no DB roundtrip) that reads the configured URL via
loadConfig() and flags the footgun: port 6543 with prepared statements
still enabled. Warns with the exact env override (GBRAIN_PREPARE=false)
and URL-query alternative (?prepare=false). Works for both the module
singleton and worker-instance engines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- test/resolve-prepare.test.ts: 11 cases covering env override, URL
  query param, port auto-detect, malformed URLs, postgres:// scheme,
  URL-encoded credentials. Uses bun:test — #284's original vitest file
  would never have run in this project.
- test/postgres-engine.test.ts: new source-level grep case asserting
  the worker-pool connect() branch calls db.resolvePrepare(url) and
  includes a typeof prepare === 'boolean' check. Mirrors the existing
  SET LOCAL regression guard. If anyone rips out the wiring, the build
  fails before shipping starts dropping rows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@garrytan garrytan changed the title fix: v0.15.3 bugs fix: v0.15.4 — PgBouncer prepare:false for Supabase transaction pooler (closes #284, #286, #270) Apr 22, 2026
@garrytan garrytan merged commit fcf40a1 into master Apr 22, 2026
4 checks passed
@notjbg

notjbg commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Thanks!

orendi84 added a commit to orendi84/gbrain that referenced this pull request Apr 22, 2026
Checks process.execPath before process.argv[1] so compiled Bun SFX
binaries don't try to spawn the virtual /$bunfs/root/gbrain path
that only exists inside the running process. Upstream garrytan/gbrain
PR garrytan#301 (commit fcf40a1, v0.15.4) landed a canonical fix for the same
bug - revert this commit after merging upstream/master.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants