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

  1. Pass a validated, non-empty orgID from an authenticated tenant context
  2. Reject requests earlier: return 401/400 at the handler when the org claim is absent
  3. 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

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


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)