JuliusBrussee/caveman · error

tenantkey: safe organization and project scope are required

Error message

tenantkey: safe organization and project scope are required

What it means

tenantkey builds tenant-scoped cache keys as colon-delimited strings like 'cave:<namespace>:<orgID>:<projectID>'. It rejects organizationID or projectID values that are empty, whitespace-only, or contain ':', CR, LF, or NUL, because such values would collide with or forge other tenants' key namespaces. projectPrefix throws this error when safePart fails for either scope component passed by CavePlan, MonthlySpend, or MonthlySpendEvent.

Solutions

  1. Log the organizationID and projectID values at the call site and fix whatever supplies them (config loader, DB record, request parsing) so both are non-empty and free of ':' before calling tenantkey functions
  2. Validate identifiers at your API boundary: reject values that are empty after TrimSpace or contain ':', '\r', '\n', or '\x00'
  3. If the ID legitimately contains ':' (e.g. a composed identifier), map it to a safe canonical form (UUID, numeric ID) before building the key

Example fix

// before
key, err := tenantkey.CavePlan(cfg.OrgID, cfg.ProjectID) // both may be ""
// after
org, proj := strings.TrimSpace(cfg.OrgID), strings.TrimSpace(cfg.ProjectID)
if org == "" || proj == "" || strings.ContainsAny(org+proj, ":\r\n\x00") {
    return fmt.Errorf("invalid tenant scope: org=%q project=%q", org, proj)
}
key, err := tenantkey.CavePlan(org, proj)
Defensive patterns

Strategy: validation

Validate before calling

func validScope(s string) bool {
	s = strings.TrimSpace(s)
	return s != "" && !strings.ContainsAny(s, ":\r\n\x00")
}
if !validScope(orgID) || !validScope(projectID) { return errors.New("invalid tenant scope") }

Prevention

When it happens

Trigger: Calling tenantkey.CavePlan, tenantkey.MonthlySpend, or tenantkey.MonthlySpendEvent with an empty organizationID or projectID, a value that is only whitespace, or a value containing ':' (e.g. a raw UUID with colons or an unvalidated user-supplied identifier).

Common situations: Config or environment variables for org/project scope left unset so they arrive as empty strings; upstream APIs returning identifiers with colon separators; records where the org/project field was never populated; trimming failures where a whitespace-only ID passes naive emptiness checks elsewhere.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/3a39e5e86aa04054. Report an issue: GitHub.

Appendix: source

Thrown at shared/platform/tenantkey/tenantkey.go:45

	}
	return prefix + ":" + month, nil
}

// MonthlySpendEvent returns idempotency-marker key for one spend event.
func MonthlySpendEvent(organizationID, projectID, month, requestID string) (string, error) {
	prefix, err := projectPrefix("spend-event", organizationID, projectID)
	if err != nil {
		return "", err
	}
	if !safePart(month) || !safePart(requestID) {
		return "", errors.New("tenantkey: safe month and request id are required")
	}
	return prefix + ":" + month + ":" + requestID, nil
}

func projectPrefix(namespace, organizationID, projectID string) (string, error) {
	if !safePart(organizationID) || !safePart(projectID) {
		return "", errors.New("tenantkey: safe organization and project scope are required")
	}
	return "cave:" + namespace + ":" + organizationID + ":" + projectID, nil
}

func safePart(value string) bool {
	value = strings.TrimSpace(value)
	return value != "" && !strings.ContainsAny(value, ":\r\n\x00")
}

View on GitHub (pinned to 3ee70a1026)