Skip to content

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

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

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>
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.

1 participant