JuliusBrussee/caveman · critical
postgres: tenant schema isolation incomplete
Error message
postgres: tenant schema isolation incomplete: %s
What it means
ValidateTenantSchema compares the inspected tenant tables and foreign keys against the required tenant-isolation contract via tenantSchemaViolations. If any violations are found, it fails startup with a semicolon-joined list of what is missing or wrong in the tenant schema. This is a deliberate migration/schema-drift guard: the database schema does not match what the library requires.
Solutions
- Read the joined violation list in the error — it names each missing table, column, or offending foreign key
- Run the project's pending migrations (e.g. migrate up) so the tenant schema matches the expected version
- Fix any cross-tenant foreign keys reported by dropping/recreating them with the tenant column in the key
- Verify you are pointing at the intended database/DSN (not an old environment)
- Re-run NewRuntimePool and confirm ValidateTenantSchema passes
Defensive patterns
Strategy: validation
Validate before calling
// before NewRuntimePool, check migrations are current
var dirty bool
if err := db.QueryRow("SELECT bool_or(version IS NULL) FROM schema_migrations").Scan(&dirty); err != nil || dirty { return errors.New("run migrations first") } Try / catch
if err := NewRuntimePool(ctx, cfg); err != nil {
if strings.Contains(err.Error(), "tenant schema isolation incomplete") {
log.Fatalf("apply pending migrations before starting: %v", err)
}
return err
} Prevention
- Always run schema migrations as a deploy step before starting services
- Treat the tenant schema contract as versioned; add migration tests asserting required tables/FKs
- Never hand-edit tenant tables in production
When it happens
Trigger: Calling NewRuntimePool against a database where the tenant schema is incomplete: missing required tenant tables, tables missing RLS/tenant columns, or foreign keys that cross tenant boundaries — tenantSchemaViolations returns a non-empty list.
Common situations: Migrations not applied (or partially applied) before starting the service; a migration added a table without the tenant-id column or without proper FK scoping; pointing a staging/production pool at a database created for an older schema version; someone altered the schema manually.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- postgres: organization scope is required
- postgres: resolver isolation incomplete
- envelope: organization scope is required
- envelope: tenant scope mismatch
- postgres: begin tenant schema inspection
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/59be185af82b6ce9.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/postgresconfig/postgresconfig.go:245
tx, err := pool.BeginTx(ctx, pgx.TxOptions{
IsoLevel: pgx.RepeatableRead,
AccessMode: pgx.ReadOnly,
})
if err != nil {
return fmt.Errorf("postgres: begin tenant schema inspection: %w", err)
}
defer tx.Rollback(ctx)
tables, err := inspectTenantTables(ctx, tx)
if err != nil {
return fmt.Errorf("postgres: inspect tenant tables: %w", err)
}
foreignKeys, err := inspectTenantForeignKeys(ctx, tx)
if err != nil {
return fmt.Errorf("postgres: inspect tenant foreign keys: %w", err)
}
if violations := tenantSchemaViolations(tables, foreignKeys); len(violations) > 0 {
return fmt.Errorf("postgres: tenant schema isolation incomplete: %s", strings.Join(violations, "; "))
}
if err := validateResolverSchema(ctx, tx); err != nil {
return err
}
if err := tx.Commit(ctx); err != nil {
return fmt.Errorf("postgres: finish tenant schema inspection: %w", err)
}
return nil
}
func inspectTenantTables(ctx context.Context, queryer catalogQuerier) ([]tenantTableSchema, error) {
rows, err := queryer.Query(ctx, `
SELECT c.relname,
NOT organization.attnotnull,
c.relrowsecurity,
c.relforcerowsecurity,
pol.oid IS NOT NULL,
coalesce(pol.polpermissive, false),View on GitHub (pinned to 3ee70a1026)