d2lang/d2 · error
decode icon URL for stable diagram hash: invalid offsets %d:
Error message
decode icon URL for stable diagram hash: invalid offsets %d:%d
What it means
After canonicalizing an icon URL, the walker records the value's byte span (valueStart from hashJSONValueStart to valueEnd from dec.InputOffset) so it can splice the stable form back into the output. If that span is inverted or extends past the input length, the offsets are inconsistent and this error is returned to avoid producing a corrupt hash output.
Source
Thrown at d2target/d2target.go:293
valueStart, err := hashJSONValueStart(b, int(dec.InputOffset()))
if err != nil {
return err
}
var raw json.RawMessage
if err := dec.Decode(&raw); err != nil {
return fmt.Errorf("decode icon URL for stable diagram hash: %w", err)
}
if bytes.Equal(raw, []byte("null")) {
continue
}
stable, err := stableHashURL(raw)
if err != nil {
return err
}
valueEnd := int(dec.InputOffset())
if valueEnd < valueStart || valueEnd > len(b) {
return fmt.Errorf("decode icon URL for stable diagram hash: invalid offsets %d:%d", valueStart, valueEnd)
}
out.Write(b[lastWrite:valueStart])
out.Write(stable)
lastWrite = valueEnd
}
closeToken, err := dec.Token()
if err != nil {
return err
}
if closeToken != json.Delim('}') {
return fmt.Errorf("decode diagram hash JSON: got closing token %v, want }", closeToken)
}
case '[':
for dec.More() {
if err := walkValue(); err != nil {
return err
}
}View on GitHub (pinned to 0d69dca6f5)
Solutions
- Pass the exact, unmodified byte slice that was decoded — do not re-slice or mutate it mid-walk
- Do not feed already-stable-hashed output back through the canonicalizer
- Ensure single-threaded access to the buffer during hashing
- Re-generate the diagram JSON from the compiled diagram instead of reusing cached bytes
Example fix
// before sub := b[10:] // offset mismatch out, err := StableHashWithIcons(sub) // after out, err := StableHashWithIcons(b) // full buffer, no mutation
Defensive patterns
Strategy: validation
Validate before calling
if !json.Valid(b) {
return errors.New("diagram JSON is malformed")
} Prevention
- Pass the full, unmodified byte buffer — never re-sliced input
- Do not mutate the buffer during hashing
- Never re-hash already-canonicalized output
- Avoid concurrent hashing of shared buffers
When it happens
Trigger: Stable-hashing JSON where hashJSONValueStart's computed start offset and the decoder's end offset disagree — e.g. input mutated between calls, an offset from a different buffer, or non-standard whitespace/encoding confusing offset calculation.
Common situations: Custom callers passing a sub-slice of the buffer with an offset mismatch; concurrent modification of the byte slice during hashing; feeding pre-hashed (already canonicalized) JSON back through the function.
Related errors
- decode diagram hash JSON: object key has type %T
- decode icon URL for stable diagram hash: %w
- decode diagram hash JSON: got closing token %v, want }
- decode diagram hash JSON: got closing token %v, want ]
- decode diagram hash JSON: unexpected delimiter %q
AI-assisted analysis of d2lang/d2@0d69dca6f5 (2026-08-31).
Data as JSON: /api/errors/350f1bfadd0a56d2.
Report an issue: GitHub.