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

  1. Verify PostgreSQL is running and reachable: docker compose ps / pg_isready -h <host> -p <port>.
  2. Check DATABASE_URL syntax and credentials in .env (host, port, user, password, dbname); in containers use the compose service name, not localhost.
  3. Retry after the DB is ready — first-boot races are common; add a healthcheck or depends_on condition.
  4. 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

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


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/872b4996ebda17f8. Report an issue: GitHub.