JuliusBrussee/caveman · error

cacheengine: invalid partition key

Error message

cacheengine: invalid partition key

What it means

Thrown by validatePlanRequest when request.PartitionKey fails validIdentity(key, 4096, true): it must be at most 4096 bytes and contain only characters accepted by the identity validator (empty is allowed because the flag is 'optional'). The partition key shards cache entries, so a malformed key would corrupt shard routing.

Source

Thrown at cacheengine/engine.go:276

		ProfileID:      profile.ID,
		Mode:           profile.Mode,
		Attribution:    profile.Attribution,
		PrefixSHA256:   result.PrefixSHA256,
		EconomicsBasis: economicsBasis,
		KeyShardCount:  1,
		Warnings:       warnings,
	}, nil
}

func validatePlanRequest(request PlanRequest) error {
	if !validIdentity(request.Scope, 4096, false) {
		return errors.New("cacheengine: invalid scope")
	}
	if !validIdentity(request.Epoch, 4096, false) {
		return errors.New("cacheengine: invalid epoch")
	}
	if !validIdentity(request.PartitionKey, 4096, true) {
		return errors.New("cacheengine: invalid partition key")
	}
	if request.ExpectedCalls < 0 || request.ExpectedRequestsPerMinute < 0 {
		return errors.New("cacheengine: negative traffic expectation")
	}
	profile := normalizedProfile(request.Profile)
	if !validIdentity(profile.ID, 256, false) || !validIdentity(profile.Provider, 64, true) || !validIdentity(profile.OptimizerID, 256, true) {
		return errors.New("cacheengine: invalid profile identity")
	}
	if profile.Mode != ModeUnsupported && profile.Mode != ModeImplicit && profile.Mode != ModeAffinity && profile.Mode != ModeExplicit {
		return fmt.Errorf("cacheengine: unknown mode %q", profile.Mode)
	}
	if profile.Mode != ModeUnsupported {
		if profile.MaxBreakpoints <= 0 || profile.MinPrefixTokens < 0 || profile.MaxRPMPerKey < 0 || profile.TTL < 0 {
			return errors.New("cacheengine: invalid cache thresholds")
		}
		switch profile.Attribution {
		case AttributionNone, AttributionOrganic, AttributionAffinity, AttributionCausal:
		default:

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Shrink or hash the partition key (e.g. hex(sha256(rawKey))) so it stays under 4096 bytes
  2. Inspect PartitionKey for control characters or invalid UTF-8 before the call and sanitize it
  3. If the key is derived from user input, truncate or map it through a fixed-size identifier

Example fix

// before
req := cacheengine.PlanRequest{Scope: scope, Epoch: epoch, PartitionKey: tenantID + ":" + rawUserPrompt}

// after
sum := sha256.Sum256([]byte(tenantID + ":" + rawUserPrompt))
req := cacheengine.PlanRequest{Scope: scope, Epoch: epoch, PartitionKey: tenantID + ":" + hex.EncodeToString(sum[:])}
Defensive patterns

Strategy: validation

Validate before calling

func validPartitionKey(k string) bool {
    return len(k) <= 4096 // plus the identity charset used for PartitionKey (optional -> empty ok)
}

Type guard

func isPartitionKeySafe(k string) bool { return len(k) <= 4096 && utf8.ValidString(k) }

Try / catch

if err := engine.Plan(req); err != nil {
    if strings.Contains(err.Error(), "invalid partition key") { /* rehash/shrink key, retry once */ }
    return err
}

Prevention

When it happens

Trigger: Calling the engine's Plan entry point with a PartitionKey longer than 4096 bytes, containing characters outside the accepted identity charset, or otherwise failing validIdentity (e.g. control characters, invalid UTF-8) while Scope and Epoch already passed.

Common situations: Building the partition key by concatenating unbounded user data (tenant + session + prompt hash), passing a full prompt or URL as the key, or an encoding bug that embeds raw bytes/NULs into the key.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/115568af233fdbc0. Report an issue: GitHub.