JuliusBrussee/caveman · error
postgres: organization scope is required
Error message
postgres: organization scope is required
What it means
WithOrg opens a transaction and sets a transaction-local tenant GUC that RLS policies read; it rejects empty org IDs up front. Passing an empty scope would run tenant queries without the RLS boundary, silently exposing cross-tenant data, so the library refuses to degrade to an unscoped transaction.
Solutions
- Pass a validated, non-empty orgID from an authenticated tenant context
- Reject requests earlier: return 401/400 at the handler when the org claim is absent
- In jobs, load the org ID explicitly and fail the job if it is missing rather than calling WithOrg unscoped
Example fix
// before
err := postgresconfig.WithOrg(ctx, pool, req.OrgID, func(tx pgx.Tx) error {...}) // OrgID == ""
// after
if strings.TrimSpace(req.OrgID) == "" {
return echo.NewHTTPError(http.StatusBadRequest, "organization scope required")
}
err := postgresconfig.WithOrg(ctx, pool, req.OrgID, func(tx pgx.Tx) error {...}) Defensive patterns
Strategy: validation
Validate before calling
if strings.TrimSpace(orgID) == "" {
return errors.New("organization scope required before tenant transaction")
} Type guard
func hasOrgScope(ctx context.Context) (string, bool) {
orgID, ok := ctx.Value(orgIDKey{}).(string)
return orgID, ok && strings.TrimSpace(orgID) != ""
} Try / catch
err := postgresconfig.WithOrg(ctx, pool, orgID, func(tx pgx.Tx) error {...})
if err != nil && strings.Contains(err.Error(), "organization scope is required") {
return status.Error(codes.FailedPrecondition, "missing tenant scope")
} Prevention
- Extract orgID from an authenticated context and reject requests missing the claim
- Never read orgID from optional/untrusted input for tenant-scoped work
- In background jobs, load the org explicitly and abort when absent
- Write a lint/test that every WithOrg call site sources orgID from a validated tenant context
When it happens
Trigger: Calling WithOrg(ctx, pool, "", fn) or with a whitespace-only orgID; orgID sourced from an unauthenticated request context, a missing JWT claim, or a struct field left at its zero value.
Common situations: Background jobs that skip tenant extraction from the request; API handlers reading orgID from an optional claim that some tokens lack; test helpers passing "" because a fixture forgot to set the tenant.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- postgres: expected runtime role is required
- postgres: runtime current_user
- postgres: runtime identity
- postgres: tenant schema isolation incomplete
- postgres: unsafe runtime identity
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/6512cab43a38cb6d.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/postgresconfig/postgresconfig.go:767
)
SELECT coalesce(string_agg(violation, '; ' ORDER BY violation), '') FROM violations
`).Scan(&violations)
if err != nil {
return fmt.Errorf("postgres: inspect resolver schema: %w", err)
}
if violations != "" {
return fmt.Errorf("postgres: resolver isolation incomplete: %s", violations)
}
return nil
}
// WithOrg runs fn inside a transaction whose tenant GUC is transaction-local.
// Empty scopes are rejected: tenant work must never degrade to an unscoped
// query when RLS is the hard boundary.
func WithOrg(ctx context.Context, pool *pgxpool.Pool, orgID string, fn func(pgx.Tx) error) error {
orgID = strings.TrimSpace(orgID)
if orgID == "" {
return errors.New("postgres: organization scope is required")
}
tx, err := pool.Begin(ctx)
if err != nil {
return err
}
defer func() { _ = tx.Rollback(ctx) }()
if _, err := tx.Exec(ctx, `SELECT set_config('app.current_organization_id', $1, true)`, orgID); err != nil {
return fmt.Errorf("postgres: set organization scope: %w", err)
}
if err := fn(tx); err != nil {
return err
}
return tx.Commit(ctx)
}
View on GitHub (pinned to 3ee70a1026)