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
- Read the wrapped '%w' clause — it names the exact rule broken; fix that field in the offending message.
- Check message index reported by '%d' against the row's input array to locate the bad entry.
- If the corpus is legitimately larger, raise the relevant limit in CorpusLimits (MaxMessageBytes) and re-run.
- Re-emit the corpus from its source (export path) instead of hand-editing JSONL.
- 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
- Run validateCorpusMessage over all rows before starting a benchmark run.
- Generate corpora only through the tool's export path; never hand-edit JSONL.
- Pin CorpusLimits alongside the corpus file so limits travel with the data.
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
- cachebench: nil corpus reader
- message exceeds byte limit
- tool message requires tool_call_id
- cachebench: invalid corpus
- cachebench: no providers
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/968adf54f20df84f.
Report an issue: GitHub.