{"record":{"id":"857bfd06c0e07602","repo":"gastownhall/beads","slug":"legacy-sqlite-issue-waiters-serialize-to-d-bytes","errorCode":null,"errorMessage":"legacy SQLite issue waiters serialize to %d bytes (current TEXT maximum %d)","messagePattern":"legacy SQLite issue waiters serialize to (.+?) bytes \\(current TEXT maximum (.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/migration/legacysqlite/reader.go","lineNumber":683,"sourceCode":"\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\")\n\t\tdefault:\n\t\t\ti += 5\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc validateCurrentTextBytes(issue *types.Issue) error {\n\tif len(issue.Payload) > currentTextBytes {\n\t\treturn fmt.Errorf(\"legacy SQLite issue payload is %d bytes (current TEXT maximum %d)\", len(issue.Payload), currentTextBytes)\n\t}\n\twaiters := issueops.FormatJSONStringArray(issue.Waiters)\n\tif len(waiters) > currentTextBytes {\n\t\treturn fmt.Errorf(\"legacy SQLite issue waiters serialize to %d bytes (current TEXT maximum %d)\", len(waiters), currentTextBytes)\n\t}\n\treturn nil\n}\n\nfunc decodeWaiters(raw string) ([]string, error) {\n\tvar decoded any\n\tif err := json.Unmarshal([]byte(raw), &decoded); err != nil {\n\t\treturn nil, err\n\t}\n\tvalues, ok := decoded.([]any)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"must be an array of strings\")\n\t}\n\twaiters := make([]string, len(values))\n\tfor i, value := range values {\n\t\twaiter, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"element %d is not a string\", i)","sourceCodeStart":665,"sourceCodeEnd":701,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/migration/legacysqlite/reader.go#L665-L701","documentation":"validateCurrentTextBytes also serializes the issue's Waiters list via issueops.FormatJSONStringArray and enforces the same TEXT byte limit. If the serialized waiters JSON exceeds currentTextBytes, the migration rejects the row.","triggerScenarios":"len(FormatJSONStringArray(issue.Waiters)) > currentTextBytes during validate().","commonSituations":"Legacy issues with hundreds/thousands of waiting actors (long waiter lists), producing a JSON array larger than the column limit.","solutions":["Reduce the waiter list on the legacy issue before migrating (drop stale waiters).","Increase the target TEXT limit if the schema permits.","Migrate with waiters truncated and re-add waiters post-migration."],"exampleFix":"// before\nwaiters := issue.Waiters // 10,000 entries migrated as-is\n// after\nissue.Waiters = issue.Waiters[:maxWaiters]","handlingStrategy":"validation","validationCode":"if n := len(issueops.FormatJSONStringArray(issue.Waiters)); n > currentTextBytes { return fmt.Errorf(\"waiters too large: %d > %d\", n, currentTextBytes) }","typeGuard":null,"tryCatchPattern":"if err := validateCurrentTextBytes(issue); err != nil { log.Warn(\"trimming waiters for\", issue.ID); issue.Waiters = nil }","preventionTips":["Cap waiter list length at the application layer","Prune stale waiters in the legacy system before migrating","Serialize and size-check large rows during export dry-runs"],"tags":["migration","sqlite","size-limit","waiters"],"backgroundTag":"text-column-overflow","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}