{"record":{"id":"350f1bfadd0a56d2","repo":"d2lang/d2","slug":"decode-icon-url-for-stable-diagram-hash-invalid-o","errorCode":null,"errorMessage":"decode icon URL for stable diagram hash: invalid offsets %d:%d","messagePattern":"decode icon URL for stable diagram hash: invalid offsets (.+?):(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"d2target/d2target.go","lineNumber":293,"sourceCode":"\t\t\t\tvalueStart, err := hashJSONValueStart(b, int(dec.InputOffset()))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tvar raw json.RawMessage\n\t\t\t\tif err := dec.Decode(&raw); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"decode icon URL for stable diagram hash: %w\", err)\n\t\t\t\t}\n\t\t\t\tif bytes.Equal(raw, []byte(\"null\")) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tstable, err := stableHashURL(raw)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tvalueEnd := int(dec.InputOffset())\n\t\t\t\tif valueEnd < valueStart || valueEnd > len(b) {\n\t\t\t\t\treturn fmt.Errorf(\"decode icon URL for stable diagram hash: invalid offsets %d:%d\", valueStart, valueEnd)\n\t\t\t\t}\n\t\t\t\tout.Write(b[lastWrite:valueStart])\n\t\t\t\tout.Write(stable)\n\t\t\t\tlastWrite = valueEnd\n\t\t\t}\n\t\t\tcloseToken, err := dec.Token()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif closeToken != json.Delim('}') {\n\t\t\t\treturn fmt.Errorf(\"decode diagram hash JSON: got closing token %v, want }\", closeToken)\n\t\t\t}\n\t\tcase '[':\n\t\t\tfor dec.More() {\n\t\t\t\tif err := walkValue(); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/d2target/d2target.go#L275-L311","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nsub := b[10:] // offset mismatch\nout, err := StableHashWithIcons(sub)\n// after\nout, err := StableHashWithIcons(b) // full buffer, no mutation","handlingStrategy":"validation","validationCode":"if !json.Valid(b) {\n\treturn errors.New(\"diagram JSON is malformed\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["json","hashing","offsets"],"backgroundTag":"json-offset-mismatch","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}