Skip to content

fix(config): accept host:port in POSTGRES_HOST (pooler support)#393

Merged
SimplicityGuy merged 1 commit into
mainfrom
worktree-fix-postgres-host-port
Jun 21, 2026
Merged

fix(config): accept host:port in POSTGRES_HOST (pooler support)#393
SimplicityGuy merged 1 commit into
mainfrom
worktree-fix-postgres-host-port

Conversation

@SimplicityGuy

Copy link
Copy Markdown
Owner

Problem

In the homelab deployment, every service that talks to PostgreSQL now connects through a PgBouncer pooler configured via POSTGRES_HOST="pgbouncer:6432" (host and port combined; no separate POSTGRES_PORT). On startup the services crashed with:

❌ Application error: invalid literal for int() with base 10: '6432:5432'

Observed in tableinator and brainztableinator, but it affected every PostgreSQL consumer via the shared common.config path.

Root cause

_build_postgres_connstr() in common/config.py unconditionally appended :5432 to POSTGRES_HOST:

return f"{host}:5432"   # "pgbouncer:6432" -> "pgbouncer:6432:5432"

Downstream host/port splitters then int()-ed the bogus "6432:5432" and threw. The various consumers also used inconsistent parsing (split(":", 1) vs rsplit(":", 1)), so the result was wrong even without a crash.

Fix

  • Add a single shared parse_postgres_host_port(value, default_port=5432) -> (host, port) helper in common/config.py:
    • An embedded port wins; otherwise falls back to POSTGRES_PORT (default 5432). The two ports are never concatenated.
    • Handles IPv6 hosts defensively (bracketed [::1]:6432, bare ::1), empty/None, malformed ports, and whitespace.
  • Rewrite _build_postgres_connstr() to use the helper and emit a canonical, round-trippable host:port (IPv6 bracketed).
  • Route every PostgreSQL consumer through the helper — tableinator, brainztableinator, dashboard, insights, api, api/setup, api/admin_setup, schema-init — replacing the ad-hoc split/rsplit logic (fix-one-fix-all).
  • Docs: note that POSTGRES_HOST may include a port in docs/configuration.md (incl. a new POSTGRES_PORT row) and .env.example.

Note: graphinator (Neo4j-only) and the Rust extractor (RabbitMQ-only) do not touch PostgreSQL, so they need no change.

Tests

  • New TestParsePostgresHostPort and TestBuildPostgresConnstr classes in tests/common/test_config.py covering: pgbouncer:6432(pgbouncer, 6432); bare host + POSTGRES_PORT(host, 5432); postgres:5432 with no POSTGRES_PORT(postgres, 5432); malformed/empty cases; IPv6; and the "never concatenate" regression.
  • Full affected suites pass: 2750 passed (tests/common, insights, schema-init, dashboard, api, tableinator, brainztableinator).
  • Ruff, mypy (per-service), and bandit pass via pre-commit.

Verification

Reproduced the previously-crashing startup path against POSTGRES_HOST="pgbouncer:6432":

connstr      : pgbouncer:6432
config.host  : pgbouncer:6432
host, port   : pgbouncer 6432   # no int() crash, port=6432

Also confirmed schema-init, api/setup, and api/admin_setup build correct connection params (incl. POSTGRES_PORT fallback).

🤖 Generated with Claude Code

Services that talk to PostgreSQL crashed on startup when POSTGRES_HOST
carried an embedded port (e.g. a PgBouncer pooler configured as
"pgbouncer:6432"):

    invalid literal for int() with base 10: '6432:5432'

Root cause: `_build_postgres_connstr()` unconditionally appended `:5432`
to POSTGRES_HOST, producing "pgbouncer:6432:5432", which downstream
host/port splitters then `int()`-ed and threw on.

Fix: add a single shared `parse_postgres_host_port()` helper in
common/config.py that robustly splits host/port — an embedded port wins,
otherwise POSTGRES_PORT (default 5432) is used; the two are never
concatenated. IPv6-bracketed hosts are handled defensively. Route every
PostgreSQL consumer through it (tableinator, brainztableinator, dashboard,
insights, api, api/setup, api/admin_setup, schema-init), replacing the
inconsistent ad-hoc split/rsplit logic.

Add unit tests for the parser and connstr builder, and document that
POSTGRES_HOST may include a port in docs/configuration.md and .env.example.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CmjnLX2V3bSwz1F9jvYQUY
@codecov

codecov Bot commented Jun 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions

Copy link
Copy Markdown
Contributor

E2E Coverage (chromium)

Totals Coverage
Statements: 46.21% ( 1257 / 2720 )
Lines: 46.21% ( 1257 / 2720 )

StandWithUkraine

@github-actions

Copy link
Copy Markdown
Contributor

E2E Coverage (webkit)

Totals Coverage
Statements: 46.21% ( 1257 / 2720 )
Lines: 46.21% ( 1257 / 2720 )

StandWithUkraine

@github-actions

Copy link
Copy Markdown
Contributor

E2E Coverage (firefox)

Totals Coverage
Statements: 46.21% ( 1257 / 2720 )
Lines: 46.21% ( 1257 / 2720 )

StandWithUkraine

@github-actions

Copy link
Copy Markdown
Contributor

E2E Coverage (webkit - iPhone 15)

Totals Coverage
Statements: 46.21% ( 1257 / 2720 )
Lines: 46.21% ( 1257 / 2720 )

StandWithUkraine

@github-actions

Copy link
Copy Markdown
Contributor

E2E Coverage (webkit - iPad Pro 11)

Totals Coverage
Statements: 46.21% ( 1257 / 2720 )
Lines: 46.21% ( 1257 / 2720 )

StandWithUkraine

@SimplicityGuy SimplicityGuy merged commit b67a302 into main Jun 21, 2026
46 of 48 checks passed
@SimplicityGuy SimplicityGuy deleted the worktree-fix-postgres-host-port branch June 21, 2026 17:50
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