Skip to content

fix(migrations): propagate GBRAIN_DATABASE_URL + GBRAIN_DISABLE_DIRECT_POOL to migration subprocesses (IPv6 band-aid)#1005

Closed
diazMelgarejo wants to merge 1 commit into
garrytan:masterfrom
diazMelgarejo:diazMelgarejo-ipv6-fix
Closed

fix(migrations): propagate GBRAIN_DATABASE_URL + GBRAIN_DISABLE_DIRECT_POOL to migration subprocesses (IPv6 band-aid)#1005
diazMelgarejo wants to merge 1 commit into
garrytan:masterfrom
diazMelgarejo:diazMelgarejo-ipv6-fix

Conversation

@diazMelgarejo

Copy link
Copy Markdown

Problem

gbrain apply-migrations --yes fails on networks where Supabase's direct connection (db.<ref>.supabase.co:5432) is IPv6-only — which is the default for most Supabase projects.

Root cause chain

  1. User configures gbrain with a Session Pooler URL (port 6543, IPv4-accessible)
  2. connection-manager.ts:deriveDirectUrl() detects a pooler URL and auto-derives db.<ref>.supabase.co:5432 for DDL operations
  3. apply-migrations.ts runs orchestrators; orchestrators spawn child subprocesses via execSync('gbrain init --migrate-only', { env: process.env })
  4. Child subprocesses inherit process.env — but this may not contain GBRAIN_DATABASE_URL (it was loaded from ~/.gbrain/config.json at parent startup, not re-exported to env)
  5. Child processes re-connect using the config file URL, connection-manager.ts derives the IPv6-only direct URL, and ddl() calls fail with ECONNREFUSED
parent process (gbrain apply-migrations)
  └─ reads config.json → GBRAIN_DATABASE_URL = pooler:6543 (IPv4) ✓
  └─ spawns: execSync('gbrain init --migrate-only', { env: process.env })
               └─ child reads config.json → derives db.<ref>.supabase.co:5432
               └─ ddl() → connect to port 5432 → ECONNREFUSED (IPv6-only) ✗

Repro

  • Supabase project (Session Pooler on port 6543)
  • IPv4-only or IPv4-preferred network (most home/office networks)
  • gbrain apply-migrations --yes → ECONNREFUSED on all orchestrator-spawned subprocesses

Related: garrytan/gstack#1301
See also: garrytan/gstack#1249 (similar IPv6-avoidance pattern in browse module)

This fix (band-aid)

Add _childEnv() to each migration orchestrator that:

  1. Reads ~/.gbrain/config.json → guarantees GBRAIN_DATABASE_URL is the pooler URL in child env
  2. Sets GBRAIN_DISABLE_DIRECT_POOL=1 → suppresses deriveDirectUrl() in the child's connection-manager
function _childEnv(): NodeJS.ProcessEnv {
  try {
    const cfg = JSON.parse(readFileSync(join(process.env.HOME || '', '.gbrain', 'config.json'), 'utf8'));
    const u = cfg.database_url || process.env.GBRAIN_DATABASE_URL;
    return { ...process.env, ...(u ? { GBRAIN_DATABASE_URL: u } : {}), GBRAIN_DISABLE_DIRECT_POOL: '1' };
  } catch { return process.env; }
}
// All execSync calls changed from: { env: process.env }
//                              to: { env: _childEnv() }

Files changed: v0.11.0, v0.12.0, v0.12.2, v0.13.0, v0.16.0, v0.18.0, v0.18.1, v0.21.0, v0.29.1

Why this is a band-aid

  • _childEnv() is duplicated across 9 files
  • Future migration orchestrators won't have it unless added manually
  • Re-reads config.json instead of using already-parsed parent state
  • GBRAIN_DISABLE_DIRECT_POOL=1 is a blunt instrument (affects ALL operations in the child, not just DDL)

The principled fix

deriveDirectUrl() should not derive a direct URL when the primary URL is already a Session Pooler (port 6543). Session Pooler maintains session state and supports DDL natively — the whole reason for the dual-pool architecture was to work around Transaction Pooler's statelessness and 2-minute statement timeout. Session Pooler has neither of those constraints.

A companion PR with that 4-line fix to connection-manager.ts makes this 126-line band-aid unnecessary.

Happy to PR the band-aid fix if useful

Verified working on:

  • macOS 26.x (Darwin 25.5.0)
  • gbrain v0.33.2.1
  • Supabase Session Pooler (port 6543, aws-1-ap-northeast-1.pooler.supabase.com)
  • Schema upgrade v24 → v57 completed successfully after this fix

…T_POOL to subprocess env

Migration orchestrators spawn child processes via execSync with
`env: process.env`. On machines where the database URL is a Supabase
Session Pooler (port 6543), connection-manager.ts auto-derives a
direct URL (`db.<ref>.supabase.co:5432`) for DDL operations. That
hostname is IPv6-only on many networks, causing ECONNREFUSED in every
migration subprocess.

Each orchestrator now calls _childEnv() which:
  1. Reads ~/.gbrain/config.json to guarantee GBRAIN_DATABASE_URL
     is set to the configured pooler URL in child env
  2. Sets GBRAIN_DISABLE_DIRECT_POOL=1 to suppress direct-URL
     derivation in the child process connection-manager

Affected: v0.11.0, v0.12.0, v0.12.2, v0.13.0, v0.16.0, v0.18.0,
          v0.18.1, v0.21.0, v0.29.1

Repro: Supabase Session Pooler (port 6543), IPv4-only or
IPv4-preferred network, run `gbrain apply-migrations --yes`.
All orchestrator-spawned subprocesses fail with ECONNREFUSED
against db.<ref>.supabase.co:5432.

See: garrytan/gstack#1301

Note: This is a band-aid. The principled fix is to not derive a
direct URL at all when the primary URL is already a Session Pooler
(port 6543), since Session Pooler supports DDL natively. See
companion PR on connection-manager.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@garrytan

garrytan commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Thanks for this contribution — and apologies for the slow triage. We did a full pass over the entire PR backlog. gbrain has moved fast, and the maintainer's larger "cathedral" rewrites have superseded a big share of community PRs: the AI gateway + recipes + user_provided_models system replaced almost all individual provider PRs; #1805 fixed the whole Postgres module-singleton class; #1542 unified the type taxonomy; #1657 the retrieval path; #1802 the doctor; and so on.

We're closing this one in that cleanup — either the fix already landed on master, it duplicates another PR or merged change, or it's outside the current merge bar. Where a closed PR carried a genuinely valuable idea, we've recorded it in docs/designs/COMMUNITY_IDEAS.md so nothing good is lost (a few may graduate into TODOs).

Please don't read the close as a judgment of the work — thank you for contributing. If you believe the underlying issue is still live on the latest master, reopen with a quick note and we'll take another look. 🙏

@garrytan garrytan closed this Jun 8, 2026
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