JuliusBrussee/caveman · error

cachebench: invalid stable identity boundary

Error message

cachebench: invalid stable identity boundary

What it means

corpusStableIdentity clamps a zero stableCount to 1 but rejects stableCount greater than the number of messages: the stable prefix must be a real leading slice of the conversation. A boundary past the end means the caller's notion of prefix length disagrees with the actual session length.

Source

Thrown at cacheengine/cachebench/corpus.go:698

	}
	sort.SliceStable(requests, func(i, j int) bool { return requests[i].At.Before(requests[j].At) })
	return Trace{
		Provider: provider,
		Scenario: Scenario{Name: corpus.Metadata.Name, Turns: len(requests), AssumedTTL: 5 * time.Minute, Step: time.Second},
		Requests: requests, TokenBasis: counter.Name() + " estimate over normalized OpenAI messages",
		TimingBasis: TimingPerPartition,
	}, nil
}

func corpusStableIdentity(messages []CorpusMessage, stableCount int) (string, error) {
	if len(messages) == 0 {
		return "", errors.New("cachebench: empty stable identity")
	}
	if stableCount == 0 {
		stableCount = 1
	}
	if stableCount > len(messages) {
		return "", errors.New("cachebench: invalid stable identity boundary")
	}
	digest := sha256.New()
	encoder := json.NewEncoder(digest)
	encoder.SetEscapeHTML(false)
	for _, message := range messages[:stableCount] {
		if err := encoder.Encode(message); err != nil {
			return "", err
		}
	}
	return hex.EncodeToString(digest.Sum(nil)), nil
}

func corpusProviderBody(provider ProviderConfig, messages []CorpusMessage) ([]byte, error) {
	switch provider.Provider {
	case "openai":
		return json.Marshal(map[string]any{"model": provider.Model, "max_completion_tokens": 256, "messages": messages})
	case "anthropic":
		return anthropicCorpusBody(provider.Model, messages)

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Clamp stableCount to min(stableCount, len(messages)) before calling if a shorter prefix is acceptable
  2. Validate the configured prefix depth against the shortest session and lower it or exclude tiny sessions
  3. Fix off-by-one in the caller computing the boundary

Example fix

// before
id, err := corpusStableIdentity(msgs, cfg.StablePrefix) // cfg=10, len(msgs)=3

// after
n := cfg.StablePrefix
if n > len(msgs) { n = len(msgs) }
id, err := corpusStableIdentity(msgs, n)
Defensive patterns

Strategy: validation

Validate before calling

n := stableCount
if n > len(msgs) {
    n = len(msgs) // or reject explicitly if a fixed depth is required
}
if n > len(msgs) {
    return fmt.Errorf("stable prefix %d exceeds session length %d", stableCount, len(msgs))
}
id, err := corpusStableIdentity(msgs, n)

Type guard

func validStableBoundary(msgs []CorpusMessage, n int) bool { return len(msgs) > 0 && n <= len(msgs) }

Try / catch

if _, err := corpusStableIdentity(msgs, cfg.Prefix); err != nil {
    if cfg.Prefix > len(msgs) {
        cfg.Prefix = len(msgs) // degrade gracefully for short sessions
    }
}

Prevention

When it happens

Trigger: Calling corpusStableIdentity(messages, N) with N > len(messages) — e.g. a fixed prefix window (say 10) applied to a short 3-message session, or an off-by-one where stableCount was set to len(messages)+1.

Common situations: Configured stable-prefix depth larger than the shortest session in the corpus; loop bounds using <= instead of <; prefix depth carried over from a different corpus with longer sessions.

Related errors


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