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
- 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
- Validate identifiers at your API boundary: reject values that are empty after TrimSpace or contain ':', '\r', '\n', or '\x00'
- 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
- Validate org/project IDs at the API/config boundary before they reach key-building code
- Never compose cache keys from raw user input containing ':'
- Trim identifiers centrally in your config loader so whitespace-only values fail early
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
- envelope: organization scope is required
- arm must be or
- CAVEMAN_CCR_MAX_BYTES must be a positive integer
- ccr max storage bytes must be positive
- ccr: unknown lifecycle
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)