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

  1. Ensure candidates come from the exact same body bytes passed to Replace and are recomputed after any modification
  2. Sort candidates by Start and merge/drop overlaps before calling Replace
  3. Skip zero-length or inverted spans at collection time
  4. 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

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


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, nil

View on GitHub (pinned to 3ee70a1026)