vxcontrol/pentagi · error

failed to resolve schema of extension %q: %w

Error message

failed to resolve schema of extension %q: %w

What it means

extensionSchema queries pg_extension/pg_namespace to find which schema an extension is installed in (returning "" when absent). This error wraps any failure of that catalog lookup other than 'no rows' — typically the connection died, the statement was canceled by the context, or the advisory-lock session was terminated. It is an infrastructure error, not a configuration error.

Source

Thrown at backend/pkg/database/tenant.go:155

		return nil
	}
}

// extensionSchema returns the schema an extension is installed into, or "" when
// it is not installed.
func extensionSchema(ctx context.Context, conn *sql.Conn, ext string) (string, error) {
	var schema string
	err := conn.QueryRowContext(ctx, `
		SELECT n.nspname
		  FROM pg_extension e
		  JOIN pg_namespace n ON n.oid = e.extnamespace
		 WHERE e.extname = $1`, ext).Scan(&schema)

	switch {
	case errors.Is(err, sql.ErrNoRows):
		return "", nil
	case err != nil:
		return "", fmt.Errorf("failed to resolve schema of extension %q: %w", ext, err)
	default:
		return schema, nil
	}
}

// VerifySearchPath asserts that connections really do resolve into the expected
// schema. A typo in the DSN would otherwise route a tenant silently onto public,
// where every tenant would share one dataset — a quiet, catastrophic failure.
func VerifySearchPath(ctx context.Context, db *sql.DB, cfg *config.Config) error {
	if !cfg.HasTenant() {
		return nil
	}

	var current sql.NullString
	if err := db.QueryRowContext(ctx, "SELECT current_schema()").Scan(&current); err != nil {
		return fmt.Errorf("failed to resolve current schema: %w", err)
	}
	if current.String != cfg.SchemaName() {

View on GitHub (pinned to ea665308ba)

Solutions

  1. Retry the startup — the whole bootstrap is idempotent (IF NOT EXISTS everywhere) and advisory-lock protected.
  2. Increase the context timeout / startup grace period if the DB is slow.
  3. Check DB logs and pooler (pgbouncer) idle/session timeout settings; keep the bootstrap session on a direct connection.
  4. Inspect the wrapped driver error for the precise transport cause (EOF, canceled, TLS).

Example fix

// before
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
// after
ctx, cancel := context.WithTimeout(ctx, 30*time.Second) // allow slow catalog lookups under load
Defensive patterns

Strategy: retry

Validate before calling

// cheap connectivity check before bootstrap
db.PingContext(ctx) // if this passes, the catalog query itself only fails on transient faults

Try / catch

err := EnsureTenantSchema(ctx, cfg)
for attempt := 1; err != nil && strings.Contains(err.Error(), "failed to resolve schema") && attempt <= 3; attempt++ {
    time.Sleep(time.Duration(attempt) * 2 * time.Second)
    err = EnsureTenantSchema(ctx, cfg)
}

Prevention

When it happens

Trigger: During tenant bootstrap, SELECT n.nspname FROM pg_extension e JOIN pg_namespace n ... WHERE e.extname=$1 fails: context deadline/cancel fired, connection reset by pooler or server restart, or the session was terminated while holding the advisory lock.

Common situations: Bootstrap taking longer than the caller's context timeout on a slow/loaded DB; pgbouncer killing idle sessions between lock acquisition and the query; network blip or failover mid-bootstrap.

Related errors


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