JuliusBrussee/caveman · error

postgres: organization scope is required

Error message

postgres: organization scope is required

What it means

postgresconfig.WithOrg wraps tenant work in a transaction and sets app.current_organization_id (transaction-local) so row-level security has a scope. It rejects an empty or whitespace orgID up front because an unscoped transaction would let a query degrade to unfiltered access — the opposite of what the RLS boundary exists for. The error is thrown before pool.Begin, so no database resources are touched.

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 766dce6b13)

Solutions

  1. Resolve the organization ID before the call and fail the request when it is missing (401/400) instead of attempting tenant work
  2. Fix the middleware order so the org context value is populated for every route that reaches WithOrg
  3. For non-tenant (platform-level) work, use an explicit non-WithOrg code path rather than passing an empty scope

Example fix

// before
orgID, _ := ctx.Value(orgKey).(string)
err := postgresconfig.WithOrg(ctx, pool, orgID, fn)

// after
orgID, ok := ctx.Value(orgKey).(string)
if !ok || strings.TrimSpace(orgID) == "" {
	return errors.New("organization context missing")
}
err := postgresconfig.WithOrg(ctx, pool, orgID, fn)
Defensive patterns

Strategy: validation

Validate before calling

orgID, ok := ctx.Value(orgContextKey).(string)
if !ok || strings.TrimSpace(orgID) == "" {
	return errors.New("organization context required")
}

Type guard

func hasOrgScope(ctx context.Context) bool {
	orgID, ok := ctx.Value(orgContextKey).(string)
	return ok && strings.TrimSpace(orgID) != ""
}

Try / catch

if err := postgresconfig.WithOrg(ctx, pool, orgID, fn); err != nil {
	if strings.Contains(err.Error(), "organization scope is required") {
		// request reached tenant code without an org ID; check middleware order
	}
	return err
}

Prevention

When it happens

Trigger: Calling WithOrg(ctx, pool, "") or WithOrg(ctx, pool, " ") — typically the org ID was extracted from a request context, JWT claim, or URL path that was absent (middleware did not set it, anonymous route, or upstream parsing produced an empty string).

Common situations: An endpoint registered before the org-extraction middleware; a background job invoked without the per-tenant parameter; a refactor switching from org slug to UUID leaving old callers passing ""; tests exercising the handler directly without seeding the org context value.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/6512cab43a38cb6d. Report an issue: GitHub.