JuliusBrussee/caveman · error
json splice: replacements for candidates
Error message
json splice: %d replacements for %d candidates
What it means
jsonsplice.Replace splices candidate string replacements into a JSON body byte-for-byte and requires a 1:1 mapping between candidates and replacement values. If the lengths differ it refuses to proceed because it cannot determine which candidate each replacement belongs to.
Solutions
- Build candidates and replacements in a single loop so their lengths always match
- Filter both slices together (e.g. pair them in a temp slice of structs) instead of independently
- When everything is unchanged, pass empty (not nil-mismatched) slices or skip the call
- Add an assertion/log before the call when lengths diverge to find the filtering bug
Example fix
// before: independent filtering
var cands []Candidate; var reps [][]byte
for _, c := range all { if !bytes.Equal(c.Rep, c.Original) { cands = append(cands, c) } }
reps = allReps
out, err := Replace(body, cands, reps)
// after: paired filtering
type pair struct{ c Candidate; r []byte }
var kept []pair
for _, p := range pairs { if !bytes.Equal(p.r, p.c.Original) { kept = append(kept, p) } }
cands := make([]Candidate, len(kept)); reps := make([][]byte, len(kept))
for i, p := range kept { cands[i], reps[i] = p.c, p.r }
out, err := Replace(body, cands, reps) Defensive patterns
Strategy: validation
Validate before calling
if len(candidates) != len(replacements) { return fmt.Errorf("candidate/replacement mismatch: %d vs %d", len(candidates), len(replacements)) } Try / catch
out, err := jsonsplice.Replace(body, cands, reps)
if err != nil {
if strings.Contains(err.Error(), "replacements for") {
log.Printf("splice count mismatch: cands=%d reps=%d", len(cands), len(reps))
}
return nil, err
} Prevention
- Always construct candidates and replacements in the same loop
- Filter with paired structs, then split into the two slices
- Return the original body when nothing changed instead of calling Replace
When it happens
Trigger: Calling Replace(body, candidates, replacements) where len(replacements) != len(candidates), typically after filtering candidates (skipping unchanged strings) without filtering replacements in parallel.
Common situations: OpenAI-style compression passes that drop candidates equal to their original but forget to drop matching replacements; off-by-one when building replacement slices; nil-vs-empty slice handling when no strings changed.
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
- openai compress: replacements for segments
- 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/56efc83686765bd7.
Report an issue: GitHub.
Appendix: source
Thrown at proxy/providers/jsonsplice/jsonsplice.go:105
}
var out string
if json.Unmarshal(body[span.Start:span.End], &out) != nil {
return "", false
}
return out, true
}
func StringField(body []byte, object Span, name string) (string, bool) {
span, ok := Field(body, object, name)
if !ok {
return "", false
}
return String(body, span)
}
func Replace(body []byte, candidates []Candidate, replacements [][]byte) ([]byte, error) {
if len(candidates) != len(replacements) {
return nil, fmt.Errorf("json splice: %d replacements for %d candidates", len(replacements), len(candidates))
}
var out []byte
last := 0
for i, candidate := range candidates {
if candidate.Start < last || candidate.End > len(body) || candidate.Start >= candidate.End {
return nil, fmt.Errorf("json splice: invalid range")
}
replacement := replacements[i]
if replacement == nil || bytes.Equal(replacement, candidate.Original) {
continue
}
quoted, err := quote(string(replacement))
if err != nil {
return nil, err
}
if out == nil {
out = make([]byte, 0, len(body)-len(candidate.Original)+len(replacement))
}View on GitHub (pinned to 3ee70a1026)