fix(connection-manager): strip .<project-ref> suffix from username when deriving direct URL

`deriveDirectUrl()` correctly rewrites the host (`aws-0-us-east-1.pooler.supabase.com`
→ `db.abcxyz.supabase.co`) but preserves the full pooler-form username
(`postgres.abcxyz`). Supabase direct connections expect a bare `postgres`
username — Supavisor uses the `.<ref>` suffix for tenant routing, but it's
not a real database user. The auto-derived URL therefore fails to authenticate
even with the correct password:

    password authentication failed for user "postgres.abcxyz"

Strip the suffix to `postgres` whenever the project-ref was successfully
extracted (same condition that triggers the host rewrite). The non-pooler
username branch is unaffected — preserved as-is to keep the port-only
fallback case working.

Hit while exercising v0.30.1's dual-pool routing on a real Supabase brain;
the kill switch (`GBRAIN_DISABLE_DIRECT_POOL=1`) papered over it locally
but every Supabase user with a stock pooler URL would silently fall through
to single-pool until the user-supplied a `GBRAIN_DIRECT_DATABASE_URL`
override. With this fix, dual-pool works out of the box for the canonical
Supabase shape.

Test additions:
  - 1 case asserting bare `postgres:secret@` in the derived URL when
    project-ref is parseable from the pooler URL (the new behavior)
  - extends the existing "falls back to port-only" case with an
    assertion that non-pooler usernames are preserved (unchanged behavior)

`bun run typecheck` clean. `deriveDirectUrl` test block passes 5/5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Brandon Lipman
2026-05-08 20:19:32 -04:00
co-authored by Claude Opus 4.7
parent dffb607ef7
commit ddf2c6a9a0
2 changed files with 21 additions and 2 deletions
+8 -2
View File
@@ -142,16 +142,22 @@ export function deriveDirectUrl(url: string): string | null {
const decodedUser = decodeURIComponent(user);
const refMatch = decodedUser.match(/^postgres\.([a-z0-9]+)$/i);
let directHost = hostname;
let directUser = parsed.username;
if (refMatch && refMatch[1] && isPoolerHost) {
directHost = `db.${refMatch[1]}.supabase.co`;
// Supabase direct connections use bare `postgres`; the `postgres.<ref>`
// form is pooler-only (Supavisor uses the suffix for tenant routing).
// Without this strip, direct auth fails with `password authentication
// failed for user "postgres.<ref>"` even though the password is correct.
directUser = 'postgres';
}
// Compose direct URL by swapping host + port. Preserve auth, db, query.
parsed.hostname = directHost;
parsed.port = '5432';
// Reconstruct with the original scheme.
const scheme = url.match(/^postgres(?:ql)?:\/\//i)?.[0] ?? 'postgres://';
const auth = parsed.username
? `${parsed.username}${parsed.password ? `:${parsed.password}` : ''}@`
const auth = directUser
? `${directUser}${parsed.password ? `:${parsed.password}` : ''}@`
: '';
const search = parsed.search ?? '';
const path = parsed.pathname ?? '';
+13
View File
@@ -44,6 +44,18 @@ describe('deriveDirectUrl', () => {
expect(direct).toContain(':secret@'); // creds preserved
});
test('strips .<project-ref> suffix from username when going pooler→direct', () => {
// Supabase direct connections require bare `postgres`; the `postgres.<ref>`
// form is pooler-only (Supavisor uses the suffix for tenant routing).
// Without the strip, direct auth fails with "password authentication
// failed for user postgres.<ref>" even with the correct password.
const direct = deriveDirectUrl(
'postgresql://postgres.abcxyz:secret@aws-0-us-east-1.pooler.supabase.com:6543/postgres'
);
expect(direct).toContain('postgres:secret@'); // bare username
expect(direct).not.toContain('postgres.abcxyz:secret@'); // no pooler suffix
});
test('falls back to port-only swap when project-ref unparseable', () => {
const direct = deriveDirectUrl(
'postgresql://customuser:secret@some.pooler.supabase.com:6543/db'
@@ -51,6 +63,7 @@ describe('deriveDirectUrl', () => {
expect(direct).toBeTruthy();
expect(direct).toContain(':5432');
expect(direct).toContain('some.pooler.supabase.com'); // host preserved
expect(direct).toContain('customuser:secret@'); // non-pooler username preserved
});
test('returns null for non-pooler URL', () => {