{"record":{"id":"685b57a9df198e21","repo":"JuliusBrussee/caveman","slug":"encode-report-w","errorCode":null,"errorMessage":"encode report: %w","messagePattern":"encode report: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"engine/cmd/caveman-engine/main.go","lineNumber":138,"sourceCode":"// the original bytes. Any contradictory result fails rather than guessing.\nfunc emitCompressResult(input []byte, res engine.Result, compressErr error, stdout, stderr io.Writer) error {\n\tcode := \"\"\n\tif compressErr != nil {\n\t\tif !bytes.Equal(res.Output, input) || !res.PassedThrough() || res.TokensAfter != res.TokensBefore {\n\t\t\treturn fmt.Errorf(\"unsafe recovery failure result: %w\", compressErr)\n\t\t}\n\t\tcode = \"cave_ccr_unavailable\"\n\t\tif errors.Is(compressErr, ccr.ErrBudgetExceeded) {\n\t\t\tcode = \"cave_ccr_budget_exceeded\"\n\t\t}\n\t}\n\tif _, err := stdout.Write(res.Output); err != nil {\n\t\treturn fmt.Errorf(\"write stdout: %w\", err)\n\t}\n\t// Report stays on stderr so stdout remains clean payload bytes.\n\treport, err := json.Marshal(compressReport{Result: res, Error: code})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"encode report: %w\", err)\n\t}\n\tif _, err := fmt.Fprintln(stderr, string(report)); err != nil {\n\t\treturn fmt.Errorf(\"write report: %w\", err)\n\t}\n\treturn nil\n}\n\nfunc runDetect() {\n\tinput, err := readBoundedInput(os.Stdin, maxStdinBytes)\n\tif err != nil {\n\t\tfatal(\"read stdin: %v\", err)\n\t}\n\t// Detection needs no store.\n\teng := engine.New(nil, nil)\n\tfmt.Println(eng.Detect(input))\n}\n\nfunc runRetrieve(args []string) {","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/engine/cmd/caveman-engine/main.go#L120-L156","documentation":"After writing payload bytes, emitCompressResult marshals the compressReport (Result + error code) to JSON for stderr. If json.Marshal fails, the error is wrapped as \"encode report: %w\". This is nearly impossible with plain data and signals an internal encoding problem.","triggerScenarios":"json.Marshal(compressReport{Result: res, Error: code}) returns an error — e.g. a field in Result/engine.Result cannot be serialized (unsupported type such as a channel or func slipped into the struct).","commonSituations":"A developer adds a field of an unsupported type (chan, func, or cyclic structure) to compressReport or the embedded engine.Result; custom MarshalJSON panics/returns error on unusual token counts.","solutions":["Check recently added fields on compressReport or engine.Result for non-JSON-serializable types.","Add or fix MarshalJSON on the offending type, or drop/redact the unserializable field.","Marshal the report in a unit test to catch regressions before runtime."],"exampleFix":"// before\ntype compressReport struct {\n    Notify func() `json:\"notify\"` // unsupported\n}\n// after\ntype compressReport struct {\n    Result engine.Result\n    Error  string\n}","handlingStrategy":"try-catch","validationCode":"// smoke-test report serialization in CI\nif _, err := json.Marshal(compressReport{Result: engine.Result{}, Error: \"cave_ccr_unavailable\"}); err != nil { t.Fatal(err) }","typeGuard":null,"tryCatchPattern":"if _, err := json.Marshal(report); err != nil {\n    return fmt.Errorf(\"encode report: %w\", err) // surface, then inspect %v of wrapped cause\n}","preventionTips":["Keep compressReport fields JSON-serializable (no chan/func/cycles)","Add a marshal round-trip test when extending Result","Prefer string/number fields in report structs"],"tags":["json","serialization","report"],"backgroundTag":"json-marshal-failed","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}