JuliusBrussee/caveman · error
json splice: invalid range
Error message
json splice: invalid range
What it means
jsonsplice.Replace validates each candidate span before splicing: Start must not regress past the previous candidate's end, End must be within body, and Start must be strictly less than End. Any violation means the candidate offsets do not describe ordered, in-bounds, non-empty spans of the given body.
Solutions
- Ensure candidates come from the exact same body bytes passed to Replace and are recomputed after any modification
- Sort candidates by Start and merge/drop overlaps before calling Replace
- Skip zero-length or inverted spans at collection time
- Switch to byte-offset (not rune-index) searches to compute spans
Example fix
// before: unsorted/inconsistent offsets
out, err := jsonsplice.Replace(body, cands, reps)
// after: normalize first
sort.Slice(cands, func(i, j int) bool { return cands[i].Start < cands[j].Start })
valid := cands[:0]
last := 0
for _, c := range cands {
if c.Start >= last && c.End <= len(body) && c.Start < c.End {
valid = append(valid, c); last = c.End
}
}
out, err := jsonsplice.Replace(body, valid, matchingReps(valid)) Defensive patterns
Strategy: validation
Validate before calling
func validCandidates(body []byte, cands []jsonsplice.Candidate) bool {
last := 0
for _, c := range cands {
if c.Start < last || c.End > len(body) || c.Start >= c.End { return false }
last = c.End
}
return true
} Try / catch
if !validCandidates(body, cands) { return nil, fmt.Errorf("refusing splice: invalid candidate offsets") }
out, err := jsonsplice.Replace(body, cands, reps) Prevention
- Recompute candidate offsets from the same body instance passed to Replace
- Sort by Start and drop overlaps/zero-length spans before calling
- Use byte offsets, not rune indices, when scanning UTF-8 payloads
When it happens
Trigger: Candidates computed against a different (older/transformed) body than the one passed; unsorted candidate spans; zero-length or inverted spans from a failed string scan; candidates from nested overlapping matches.
Common situations: Reusing cached offsets after the body changed upstream; sort order broken by map iteration when collecting candidates; byte-offset vs rune-offset confusion when locating strings in multibyte UTF-8 payloads.
Related errors
- json splice: invalid array range
- json splice: invalid object range
- json splice: replacements for candidates
- json splice: invalid element insertion
- json splice: invalid field insertion
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/010a9a832a6eb2a7.
Report an issue: GitHub.
Appendix: source
Thrown at proxy/providers/jsonsplice/jsonsplice.go:111
}
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))
}
out = append(out, body[last:candidate.Start]...)
out = append(out, quoted...)
last = candidate.End
}
if out == nil {
return body, nilView on GitHub (pinned to 3ee70a1026)