{"record":{"id":"51000b28a2462662","repo":"JuliusBrussee/caveman","slug":"message-exceeds-byte-limit","errorCode":null,"errorMessage":"message exceeds byte limit","messagePattern":"message exceeds byte limit","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cacheengine/cachebench/corpus.go","lineNumber":380,"sourceCode":"\tif len(message.Content) > 0 {\n\t\tvar content any\n\t\tif err := json.Unmarshal(message.Content, &content); err != nil {\n\t\t\treturn errors.New(\"content must decode\")\n\t\t}\n\t\tif content != nil {\n\t\t\tif _, ok := content.(string); !ok {\n\t\t\t\treturn errors.New(\"content must be string or null\")\n\t\t\t}\n\t\t}\n\t}\n\tfor _, call := range message.ToolCalls {\n\t\tif call.Type != \"function\" || !validBoundedText(call.ID, 2048, false) || !validBoundedText(call.Function.Name, 512, false) || !validUniqueJSONObject([]byte(call.Function.Arguments)) {\n\t\t\treturn errors.New(\"tool call requires function type, id, function name, and JSON arguments\")\n\t\t}\n\t\tmessageBytes += len(call.ID) + len(call.Type) + len(call.Function.Name) + len(call.Function.Arguments)\n\t}\n\tif messageBytes > limits.MaxMessageBytes {\n\t\treturn errors.New(\"message exceeds byte limit\")\n\t}\n\tif message.Role == \"tool\" && message.ToolCallID == \"\" {\n\t\treturn errors.New(\"tool message requires tool_call_id\")\n\t}\n\treturn nil\n}\n\nfunc appendCorpusRow(rows *[]CorpusRow, sessions map[string]bool, retainedBytes *int64, row CorpusRow, limits CorpusLimits) error {\n\tif len(*rows) >= limits.MaxRows {\n\t\treturn fmt.Errorf(\"row limit %d exceeded\", limits.MaxRows)\n\t}\n\tif !sessions[row.SessionID] {\n\t\tif len(sessions) >= limits.MaxSessions {\n\t\t\treturn fmt.Errorf(\"session limit %d exceeded\", limits.MaxSessions)\n\t\t}\n\t\tsessions[row.SessionID] = true\n\t}\n\trowBytes := corpusRowRetainedBytes(row)","sourceCodeStart":362,"sourceCodeEnd":398,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/cacheengine/cachebench/corpus.go#L362-L398","documentation":"Thrown by corpus message validation when the accumulated byte size of a message (role + content + tool call fields) exceeds CorpusLimits.MaxMessageBytes. The library enforces this bound so a single pathological corpus row cannot dominate memory or hashing during corpus analysis. It fires per message, before the row is appended to the corpus.","triggerScenarios":"Calling corpus validation (directly or via RunCorpus/BuildCorpusTrace) with a CorpusMessage whose combined len() of role, string content, and each tool call's ID/type/name/arguments sums above limits.MaxMessageBytes. Typically a message with a multi-megabyte content string or huge tool-call argument JSON.","commonSituations":"Importing a public agent trace that contains one oversized turn (e.g. a tool that dumped an entire file or base64 blob as arguments); setting MaxMessageBytes lower than the real corpus needs; miscounting bytes because tool arguments are counted raw, not compacted.","solutions":["Inspect the offending message and truncate or split oversized content/tool arguments before validation","Raise limits.MaxMessageBytes in your CorpusLimits to fit the largest legitimate message, keeping the cap sane for your memory budget","Pre-filter corpus rows with your own byte accounting (sum of len(role)+len(content)+sum of tool-call field lengths) before calling the API","If the blob is legitimate payload, move it out of the corpus and reference it by hash/pointer instead"],"exampleFix":"// before\nmsg := CorpusMessage{Role: \"user\", Content: json.RawMessage(hugeJSON)}\nerr := validateCorpusMessage(msg, limits) // message exceeds byte limit\n\n// after\nif approxMessageBytes(msg) > limits.MaxMessageBytes {\n    msg.Content = json.RawMessage(truncateTo(hugeJSON, limits.MaxMessageBytes/2))\n}\nerr := validateCorpusMessage(msg, limits)","handlingStrategy":"validation","validationCode":"func messageFits(msg CorpusMessage, limits CorpusLimits) bool {\n    n := len(msg.Role) + len(msg.Content)\n    for _, c := range msg.ToolCalls {\n        n += len(c.ID) + len(c.Type) + len(c.Function.Name) + len(c.Function.Arguments)\n    }\n    return n <= limits.MaxMessageBytes\n}\n\nfor i := range corpus.Rows {\n    for _, m := range corpus.Rows[i].Messages {\n        if !messageFits(m, limits) { /* truncate or reject */ }\n    }\n}","typeGuard":null,"tryCatchPattern":"if err := validateCorpus(msg, limits); err != nil {\n    if err.Error() == \"message exceeds byte limit\" {\n        // split or truncate the oversized message, revalidate\n    }\n    return err\n}","preventionTips":["Set MaxMessageBytes from your real corpus's p99 message size plus headroom, not a guess","Sum role+content+tool-call field lengths with the same formula the validator uses before submitting","Reject or shard tool outputs that embed whole files/base64 blobs into arguments"],"tags":["go","corpus","validation","size-limits"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}