Skip to content

symfonic.diagnostics.checks.check_postgres_dsn

check_postgres_dsn

POSTGRES_DSN reachability probe (v7.16, item 5).

Network-sensitive check that opens a TCP connection to the host:port in POSTGRES_DSN and (when possible) issues a startup-handshake to verify credentials.

Budget (architect verdict):

  • TCP connect: 2 seconds
  • Auth handshake: 1 second

The check must auto-skip when CI=true (or when the runner is invoked with --no-network). CI runners frequently lack outbound network to adopter Postgres clusters, and a 3-second probe per CI job is paper-cut latency we don't want to charge. The skip path is handled by the runner (which emits an INFO directly), so this module runs only when network checks are enabled.

Output severity is WARN (not ERROR) because the adopter may be running a development workflow with no Postgres bound -- we want to surface the issue without breaking symfonic doctor in environments where the DSN is intentionally unset.

check_postgres_dsn async

check_postgres_dsn(
    agent: SymfonicAgent,
) -> list[CheckResult]

Probe POSTGRES_DSN for TCP reachability and basic auth.

Source code in src/symfonic/diagnostics/checks/check_postgres_dsn.py
async def check_postgres_dsn(
    agent: SymfonicAgent,  # noqa: ARG001 -- uniform signature
) -> list[CheckResult]:
    """Probe ``POSTGRES_DSN`` for TCP reachability and basic auth."""
    raw = (os.environ.get("POSTGRES_DSN") or "").strip()
    if not raw:
        # No DSN configured -- nothing to probe.  Stay silent so the
        # adopter sees the embedder / layer checks rather than DSN
        # noise.
        return []

    host, port = _parse_host_port(raw)
    if host is None or port is None:
        return [
            CheckResult(
                name="postgres_dsn.malformed",
                severity=Severity.WARN,
                message=(
                    f"POSTGRES_DSN={raw!r} could not be parsed as a "
                    "host:port pair."
                ),
                fix_hint=(
                    "Expected shape: postgres://user:password@host:port/dbname"
                ),
            ),
        ]

    print(
        f"  probing POSTGRES_DSN={host}:{port} ...",
        file=sys.stderr,
        flush=True,
    )
    tcp_ok, tcp_err = await _probe_tcp(host, port)
    if not tcp_ok:
        return [
            CheckResult(
                name="postgres_dsn.unreachable",
                severity=Severity.WARN,
                message=(
                    f"POSTGRES_DSN host {host}:{port} is unreachable "
                    f"within {_CONNECT_TIMEOUT_SECONDS}s ({tcp_err})."
                ),
                fix_hint=(
                    "Verify the host is running and reachable from this "
                    "process.  Set --no-network (or CI=true) to skip this "
                    "probe in environments without outbound DB access."
                ),
            ),
        ]

    # TCP succeeded.  Auth-probe is best-effort: if asyncpg is not
    # installed we report TCP-only success at INFO; if asyncpg is
    # available, we try a connect-and-close within _AUTH_TIMEOUT_SECONDS.
    auth_result = await _probe_auth(raw)
    if auth_result is not None:
        return [auth_result]

    return []