JuliusBrussee/caveman · error
openai compress: replacements for segments
Error message
openai compress: %d replacements for %d segments
What it means
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.
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
Example fix
// before: unfiltered reps
out, err := spliceStringReplacements(body, cands, reps)
// after: pair and filter together
type seg struct{ c spliceCandidate; r []byte }
var kept []seg
for i := range cands {
if !bytes.Equal(reps[i], body[cands[i].start:cands[i].end]) {
kept = append(kept, seg{cands[i], reps[i]})
}
}
c := make([]spliceCandidate, len(kept)); r := make([][]byte, len(kept))
for i, s := range kept { c[i], r[i] = s.c, s.r }
out, err := spliceStringReplacements(body, c, r) Defensive patterns
Strategy: validation
Validate before calling
if len(cands) != len(reps) { return fmt.Errorf("segment/replacement mismatch: %d vs %d", len(cands), len(reps)) } Try / catch
out, err := spliceStringReplacements(body, cands, reps)
if err != nil {
if strings.Contains(err.Error(), "replacements for") {
log.Printf("compression splice mismatch: cands=%d reps=%d", len(cands), len(reps))
}
return nil, err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- json splice: replacements for candidates
- json splice: invalid array range
- json splice: invalid element insertion
- json splice: invalid field insertion
- json splice: invalid object range
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/aab0b4083d4d4ff2.
Report an issue: GitHub.
Appendix: source
Thrown at proxy/providers/openai/content_compress.go:644
}
func isJSONString(body []byte, span jsonSpan) bool {
return span.start < span.end && span.start >= 0 && span.end <= len(body) && body[span.start] == '"'
}
func quoteJSONStringNoHTML(s string) ([]byte, error) {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
if err := enc.Encode(s); err != nil {
return nil, err
}
return bytes.TrimSuffix(buf.Bytes(), []byte("\n")), nil
}
func spliceStringReplacements(body []byte, candidates []spliceCandidate, reps [][]byte) ([]byte, error) {
if len(reps) != len(candidates) {
return nil, fmt.Errorf("openai compress: %d replacements for %d segments", len(reps), len(candidates))
}
var out []byte
last := 0
changed := false
for i, c := range candidates {
if c.start < last || c.end > len(body) || c.start >= c.end {
return nil, fmt.Errorf("openai compress: invalid splice range")
}
rep := reps[i]
if rep == nil || bytes.Equal(rep, c.original) {
continue
}
quoted, err := quoteJSONStringNoHTML(string(rep))
if err != nil {
return nil, err
}
if !changed {
out = make([]byte, 0, len(body)-len(c.original)+len(rep))View on GitHub (pinned to 3ee70a1026)