{"record":{"id":"aab0b4083d4d4ff2","repo":"JuliusBrussee/caveman","slug":"openai-compress-d-replacements-for-d-segments","errorCode":null,"errorMessage":"openai compress: %d replacements for %d segments","messagePattern":"openai compress: (.+?) replacements for (.+?) segments","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/openai/content_compress.go","lineNumber":644,"sourceCode":"}\n\nfunc isJSONString(body []byte, span jsonSpan) bool {\n\treturn span.start < span.end && span.start >= 0 && span.end <= len(body) && body[span.start] == '\"'\n}\n\nfunc quoteJSONStringNoHTML(s string) ([]byte, error) {\n\tvar buf bytes.Buffer\n\tenc := json.NewEncoder(&buf)\n\tenc.SetEscapeHTML(false)\n\tif err := enc.Encode(s); err != nil {\n\t\treturn nil, err\n\t}\n\treturn bytes.TrimSuffix(buf.Bytes(), []byte(\"\\n\")), nil\n}\n\nfunc spliceStringReplacements(body []byte, candidates []spliceCandidate, reps [][]byte) ([]byte, error) {\n\tif len(reps) != len(candidates) {\n\t\treturn nil, fmt.Errorf(\"openai compress: %d replacements for %d segments\", len(reps), len(candidates))\n\t}\n\tvar out []byte\n\tlast := 0\n\tchanged := false\n\tfor i, c := range candidates {\n\t\tif c.start < last || c.end > len(body) || c.start >= c.end {\n\t\t\treturn nil, fmt.Errorf(\"openai compress: invalid splice range\")\n\t\t}\n\t\trep := reps[i]\n\t\tif rep == nil || bytes.Equal(rep, c.original) {\n\t\t\tcontinue\n\t\t}\n\t\tquoted, err := quoteJSONStringNoHTML(string(rep))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif !changed {\n\t\t\tout = make([]byte, 0, len(body)-len(c.original)+len(rep))","sourceCodeStart":626,"sourceCodeEnd":662,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/providers/openai/content_compress.go#L626-L662","documentation":"spliceStringReplacements splices string replacements into an OpenAI payload and, like jsonsplice.Replace, requires len(reps) == len(candidates) so each candidate maps to exactly one replacement. Mismatched lengths abort the splice.","triggerScenarios":"Content compression pipelines that drop unchanged segments (replacement equal to original) without dropping the matching candidate, or vice versa; parallel builders appending to the two slices unevenly on error paths.","commonSituations":"A compression pass filtering candidates for nil content while keeping all replacement bytes; concurrency races appending to reps; early-return error paths leaving reps shorter than candidates.","solutions":["Build candidates and replacements together in one loop; never filter one without the other","Filter with a paired struct then split into the two slices immediately before the call","When no segment changes, return the original body instead of invoking the splice","Add a length assertion/log upstream to catch divergent filters in CI"],"exampleFix":"// before: unfiltered reps\nout, err := spliceStringReplacements(body, cands, reps)\n// after: pair and filter together\ntype seg struct{ c spliceCandidate; r []byte }\nvar kept []seg\nfor i := range cands {\n    if !bytes.Equal(reps[i], body[cands[i].start:cands[i].end]) {\n        kept = append(kept, seg{cands[i], reps[i]})\n    }\n}\nc := make([]spliceCandidate, len(kept)); r := make([][]byte, len(kept))\nfor i, s := range kept { c[i], r[i] = s.c, s.r }\nout, err := spliceStringReplacements(body, c, r)","handlingStrategy":"validation","validationCode":"if len(cands) != len(reps) { return fmt.Errorf(\"segment/replacement mismatch: %d vs %d\", len(cands), len(reps)) }","typeGuard":null,"tryCatchPattern":"out, err := spliceStringReplacements(body, cands, reps)\nif err != nil {\n    if strings.Contains(err.Error(), \"replacements for\") {\n        log.Printf(\"compression splice mismatch: cands=%d reps=%d\", len(cands), len(reps))\n    }\n    return nil, err\n}","preventionTips":["Emit candidates and replacements from the same iteration over segments","Filter both together; never drop candidates while keeping all reps","Return the unmodified body when all replacements equal their originals"],"tags":["json","go","byte-splice","argument-count"],"backgroundTag":"invalid-argument-value","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"}