vxcontrol/pentagi · error
failed to create schema %q: %w
Error message
failed to create schema %q: %w
What it means
Inside the tenant-bootstrap advisory lock, the code runs CREATE SCHEMA IF NOT EXISTS <schema> (name quoted with pq.QuoteIdentifier). This error wraps any PostgreSQL rejection of that DDL — the schema name itself is pre-validated, so failures are almost always privilege or server-state problems. Bootstrap aborts and the tenant's search_path is never wired up.
Source
Thrown at backend/pkg/database/tenant.go:60
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
}
}
return nil
}); err != nil {
return err
}
// Rewrite the DSN once; every consumer reads cfg.DatabaseURL afterwards.
return RewriteDatabaseURLForTenant(cfg)
}
// RewriteDatabaseURLForTenant appends the tenant search_path to cfg.DatabaseURL.View on GitHub (pinned to ea665308ba)
Solutions
- Grant the role CREATE on the database once: GRANT CREATE ON DATABASE pentagi TO appuser; or pre-create the tenant schema as an admin.
- Confirm the DB is not in read-only mode (SHOW transaction_read_only;).
- Retry the boot — CREATE SCHEMA IF NOT EXISTS is idempotent and the advisory lock serializes concurrent runs.
- Check the wrapped pg error for the exact server reason (permission denied vs duplicate vs admin shutdown).
Example fix
// before (as appuser without DDL rights) CREATE SCHEMA IF NOT EXISTS tenant_acme; -- ERROR: permission denied // after (run once as superuser) GRANT CREATE ON DATABASE pentagi TO appuser; -- or: CREATE SCHEMA tenant_acme AUTHORIZATION appuser;
Defensive patterns
Strategy: validation
Validate before calling
// preflight as the app role, before starting the app psql "$DATABASE_URL" -c "CREATE SCHEMA IF NOT EXISTS tenant_check;" \ && psql "$DATABASE_URL" -c "DROP SCHEMA tenant_check;"
Try / catch
if err := EnsureTenantSchema(ctx, cfg); err != nil {
if strings.Contains(err.Error(), "permission denied") {
log.Error().Msg("grant CREATE ON DATABASE to the app role or pre-create the tenant schema")
}
return err
} Prevention
- Provision the app role with CREATE ON DATABASE, or pre-create tenant schemas via IaC
- Keep the DB out of read-only mode during deploys
- Connect bootstrap directly to Postgres, not through pgbouncer
- Re-run boot after fixing: CREATE SCHEMA IF NOT EXISTS is idempotent
When it happens
Trigger: The connected role lacks CREATE privilege on the database, the connection was dropped mid-bootstrap (context canceled, server restart), or a syntax/serialization issue occurs during CREATE SCHEMA IF NOT EXISTS for the tenant schema.
Common situations: Deploying with a least-privilege DB user that can connect but cannot create schemas; running bootstrap while the DB is in read-only mode; connection pooler (pgbouncer) dropping the session mid-statement; context timeout expiring before DDL completes.
Related errors
- extension %q is installed in schema %q, but multi-tenant mod
- failed to reach database for tenant bootstrap: %w
- failed to create extension %q in schema %q (a privileged use
- failed to resolve current schema: %w
- search_path resolved to schema %q, expected %q — refusing to
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/b3a23e0b07b0157b.
Report an issue: GitHub.