vxcontrol/pentagi · critical
failed to reach database for tenant bootstrap: %w
Error message
failed to reach database for tenant bootstrap: %w
What it means
EnsureTenantSchema opens a short-lived bootstrap connection on the original DATABASE_URL and pings it before doing any tenant bootstrap work. PostgreSQL's database/sql driver only parses the DSN at sql.Open time; the first real network contact happens at Ping, so this error wraps any connection failure — wrong host/port, DB down, bad credentials, TLS mismatch. It means the application cannot reach PostgreSQL at all and tenant schema setup cannot proceed.
Source
Thrown at backend/pkg/database/tenant.go:49
func EnsureTenantSchema(ctx context.Context, cfg *config.Config) error {
if !cfg.HasTenant() {
return nil
}
schema := cfg.SchemaName()
extSchema := cfg.ExtensionSchema()
// Short-lived bootstrap connection on the ORIGINAL DSN. Opening it before the
// search_path rewrite means CREATE EXTENSION resolves against the default
// path and lands in the shared schema rather than the tenant's.
db, err := sql.Open("postgres", cfg.DatabaseURL)
if err != nil {
return fmt.Errorf("failed to open bootstrap database connection: %w", err)
}
defer db.Close()
if err := db.PingContext(ctx); err != nil {
return fmt.Errorf("failed to reach database for tenant bootstrap: %w", err)
}
// Serialize concurrent first boots so two instances cannot race on schema and
// extension creation in the shared catalog.
if err := WithAdvisoryLock(ctx, db, "pentagi-tenant-bootstrap", func(conn *sql.Conn) error {
// QuoteIdentifier is belt-and-braces: ValidateTenantID already restricts
// the character set, but this keeps the statement safe if that ever relaxes.
if _, err := conn.ExecContext(ctx,
"CREATE SCHEMA IF NOT EXISTS "+pq.QuoteIdentifier(schema),
); err != nil {
return fmt.Errorf("failed to create schema %q: %w", schema, err)
}
for _, ext := range requiredExtensions {
if err := ensureSharedExtension(ctx, conn, ext, extSchema); err != nil {
return err
}
}View on GitHub (pinned to ea665308ba)
Solutions
- Verify PostgreSQL is running and reachable: docker compose ps / pg_isready -h <host> -p <port>.
- Check DATABASE_URL syntax and credentials in .env (host, port, user, password, dbname); in containers use the compose service name, not localhost.
- Retry after the DB is ready — first-boot races are common; add a healthcheck or depends_on condition.
- Inspect the wrapped driver error in the message for the precise cause (connection refused vs auth failed vs unknown database).
Example fix
// before
db, _ := sql.Open("postgres", "postgres://user:pass@localhost:5432/pentagi")
// after
db, _ := sql.Open("postgres", "postgres://user:pass@postgres:5432/pentagi?sslmode=disable") // service name + reachable creds Defensive patterns
Strategy: try-catch
Validate before calling
// before calling the library
conn, err := net.DialTimeout("tcp", "postgres:5432", 3*time.Second)
if err != nil { return fmt.Errorf("postgres unreachable: %w", err) }
// and validate DSN parses:
if _, err := url.Parse(cfg.DatabaseURL); err != nil { return err } Try / catch
if err := database.EnsureTenantSchema(ctx, cfg); err != nil {
var retryable bool
if strings.Contains(err.Error(), "connection refused") || errors.Is(ctx.Err(), context.DeadlineExceeded) {
retryable = true // wait and retry while DB container starts
}
log.Error().Err(err).Bool("retryable", retryable).Msg("tenant bootstrap failed")
} Prevention
- Add a Docker healthcheck/depends_on condition for the postgres service
- Never use localhost for the DB host inside containers
- Percent-encode special characters in the DB password inside DATABASE_URL
- Keep a startup retry loop with backoff for first boot
When it happens
Trigger: TENANT_ID is set and EnsureTenantSchema calls db.PingContext(ctx) on the original DSN and the ping fails: Postgres not running, wrong host/port in DATABASE_URL, wrong password, database does not exist, max_connections exhausted, or network/DNS failure between the container and the DB.
Common situations: Docker Compose where the postgres service is still starting (no healthcheck/wait-for), DATABASE_URL pointing at localhost inside a container instead of the service name, password containing characters that break URL parsing, pg_hba.conf rejecting the connection, or the DB container being on a different compose network.
Related errors
- failed to resolve current schema: %w
- failed to create flow in DB: %w
- failed to delete assistant %d: %w
- failed to rename flow %d: %w
- failed to update flow provider in DB: %w
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/872b4996ebda17f8.
Report an issue: GitHub.