{"record":{"id":"b5927356bac9dfec","repo":"gastownhall/beads","slug":"truncated-json-unicode-escape","errorCode":null,"errorMessage":"truncated JSON Unicode escape","messagePattern":"truncated JSON Unicode escape","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/migration/legacysqlite/reader.go","lineNumber":651,"sourceCode":"\t\t}\n\t}\n\treturn nil\n}\n\nfunc checkJSONSurrogates(raw string) error {\n\tfor i := 0; i < len(raw); i++ {\n\t\tif raw[i] != '\\\\' {\n\t\t\tcontinue\n\t\t}\n\t\tif i+1 >= len(raw) {\n\t\t\treturn fmt.Errorf(\"truncated JSON escape\")\n\t\t}\n\t\tif raw[i+1] != 'u' {\n\t\t\ti++\n\t\t\tcontinue\n\t\t}\n\t\tif i+6 > len(raw) {\n\t\t\treturn fmt.Errorf(\"truncated JSON Unicode escape\")\n\t\t}\n\t\tcode, err := strconv.ParseUint(raw[i+2:i+6], 16, 16)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid JSON Unicode escape\")\n\t\t}\n\t\tswitch {\n\t\tcase code >= 0xd800 && code <= 0xdbff:\n\t\t\tnext := i + 6\n\t\t\tif next+6 > len(raw) || raw[next] != '\\\\' || raw[next+1] != 'u' {\n\t\t\t\treturn fmt.Errorf(\"lone high UTF-16 surrogate escape\")\n\t\t\t}\n\t\t\tlow, err := strconv.ParseUint(raw[next+2:next+6], 16, 16)\n\t\t\tif err != nil || low < 0xdc00 || low > 0xdfff {\n\t\t\t\treturn fmt.Errorf(\"lone high UTF-16 surrogate escape\")\n\t\t\t}\n\t\t\ti = next + 5\n\t\tcase code >= 0xdc00 && code <= 0xdfff:\n\t\t\treturn fmt.Errorf(\"lone low UTF-16 surrogate escape\")","sourceCodeStart":633,"sourceCodeEnd":669,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/migration/legacysqlite/reader.go#L633-L669","documentation":"checkJSONSurrogates validates \\uXXXX escapes in metadata strings. If a backslash is followed by 'u' but fewer than four hex digits remain in the string, the Unicode escape is incomplete and cannot be decoded, so the migration returns 'truncated JSON Unicode escape'.","triggerScenarios":"A raw string contains '\\u' (or '\\u1') within the last 5 bytes so that i+6 exceeds len(raw).","commonSituations":"Legacy rows truncated mid-escape by a byte-limit, corrupted exports, or manual edits that cut a string in the middle of a \\uXXXX sequence.","solutions":["Locate and repair the truncated \\uXXXX escape in the legacy row (complete the four hex digits or remove the partial escape).","Re-export the legacy database without byte truncation.","Sanitize inputs to expand or strip partial Unicode escapes before migration."],"exampleFix":"// before\n\"label\": \"caf\\u00\n// after\n\"label\": \"caf\\u00e9\"","handlingStrategy":"validation","validationCode":"for i := 0; i+1 < len(raw); i++ { if raw[i]=='\\\\' && raw[i+1]=='u' && i+6 > len(raw) { return fmt.Errorf(\"truncated \\\\u escape at %d\", i) } }","typeGuard":"func completeUnicodeEscapes(s string) bool { for i := 0; i < len(s); i++ { if s[i]=='\\\\' && i+1 < len(s) && s[i+1]=='u' && i+6 > len(s) { return false } }; return true }","tryCatchPattern":null,"preventionTips":["Avoid byte-level truncation of text columns","Re-encode legacy text to direct UTF-8 rather than \\u escapes","Run a JSON validity check on exported rows before migration"],"tags":["json","unicode","escaping","migration"],"backgroundTag":"invalid-json-escape","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}