JuliusBrussee/caveman · error

message %d: %w

Error message

message %d: %w

What it means

A corpus row's input decoded successfully as JSON, but one of its messages failed per-message validation in validateCorpusMessage. The error wraps the underlying cause with the zero-based message index, so '%w' reveals the real problem (bad role, oversized content, missing tool_call_id, etc.). It is returned while converting a wire-format row into a CorpusRow, before the row ever enters the benchmark.

Source

Thrown at cacheengine/cachebench/corpus.go:337

	if wire.OutputLength < 0 || math.IsNaN(wire.PreGap) || math.IsInf(wire.PreGap, 0) || wire.PreGap < 0 || wire.PreGap > maxCorpusGapSeconds {
		return CorpusRow{}, fmt.Errorf("output_length must be non-negative and pre_gap must be within 0..%d seconds", maxCorpusGapSeconds)
	}
	if len(wire.Input) == 0 || !json.Valid(wire.Input) {
		return CorpusRow{}, errors.New("input must be valid non-empty message array")
	}
	if len(wire.SessionID)+len(wire.Model)+len(wire.Input) > limits.MaxRowBytes {
		return CorpusRow{}, fmt.Errorf("row exceeds byte limit %d", limits.MaxRowBytes)
	}
	var messages []CorpusMessage
	if err := json.Unmarshal(wire.Input, &messages); err != nil {
		return CorpusRow{}, fmt.Errorf("decode input: %w", err)
	}
	if len(messages) == 0 || len(messages) > limits.MaxMessagesPerRequest {
		return CorpusRow{}, fmt.Errorf("message count %d outside 1..%d", len(messages), limits.MaxMessagesPerRequest)
	}
	for messageIndex, message := range messages {
		if err := validateCorpusMessage(message, limits); err != nil {
			return CorpusRow{}, fmt.Errorf("message %d: %w", messageIndex, err)
		}
	}
	return CorpusRow{
		RowIndex: index, SessionID: wire.SessionID, Model: wire.Model, Input: messages,
		OutputLength: wire.OutputLength, PreGap: wire.PreGap,
	}, nil
}

func validateCorpusMessage(message CorpusMessage, limits CorpusLimits) error {
	switch message.Role {
	case "system", "developer", "user", "assistant", "tool":
	default:
		return fmt.Errorf("unsupported role %q", message.Role)
	}
	if !validBoundedText(message.ToolCallID, 2048, true) || !validBoundedText(message.Name, 512, true) {
		return errors.New("invalid message identity")
	}
	if len(message.Content) == 0 && len(message.ToolCalls) == 0 {

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Read the wrapped '%w' clause — it names the exact rule broken; fix that field in the offending message.
  2. Check message index reported by '%d' against the row's input array to locate the bad entry.
  3. If the corpus is legitimately larger, raise the relevant limit in CorpusLimits (MaxMessageBytes) and re-run.
  4. Re-emit the corpus from its source (export path) instead of hand-editing JSONL.
  5. Add a pre-flight validator over the corpus file before benchmark runs.

Example fix

// before (corpus row input)
{"role":"Tool","content":"result"}
// after
{"role":"tool","tool_call_id":"call_1","content":"result"}
Defensive patterns

Strategy: validation

Validate before calling

func preflightRow(input json.RawMessage, limits CorpusLimits) error {
	var msgs []CorpusMessage
	if err := json.Unmarshal(input, &msgs); err != nil {
		return fmt.Errorf("decode input: %w", err)
	}
	if len(msgs) == 0 || len(msgs) > limits.MaxMessagesPerRequest {
		return fmt.Errorf("message count %d outside 1..%d", len(msgs), limits.MaxMessagesPerRequest)
	}
	for i, m := range msgs {
		if err := validateCorpusMessage(m, limits); err != nil {
			return fmt.Errorf("message %d: %w", i, err)
		}
	}
	return nil
}

Try / catch

if err := corpus.LoadCorpus(path, limits); err != nil {
	var msgErr *fmt.wrapError // inspect wrapped cause via errors.Unwrap
	_ = msgErr
	log.Fatalf("corpus load failed: %v", err)
}

Prevention

When it happens

Trigger: Calling the corpus row decode path (LoadCorpus / row conversion) where row.Input unmarshals to a []CorpusMessage whose element i violates validateCorpusMessage: unsupported role, invalid tool_call_id/name bounds, empty content and tool_calls, content over MaxMessageBytes or not valid JSON, or a tool message without tool_call_id.

Common situations: Hand-edited or generated corpus JSONL with a typo'd role ('Tool', 'function'), assistant messages with neither content nor tool_calls, truncated content strings that break JSON validity, or a corpus produced for different CorpusLimits than the ones configured now.

Related errors


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