{"record":{"id":"1e4f8e85d2b6d3b5","repo":"gastownhall/beads","slug":"element-d-is-not-a-string","errorCode":null,"errorMessage":"element %d is not a string","messagePattern":"element (.+?) is not a string","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/migration/legacysqlite/reader.go","lineNumber":701,"sourceCode":"\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)\n\t\t}\n\t\tif err := checkUTF8(currentString{fmt.Sprintf(\"waiters[%d]\", i), waiter}); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\twaiters[i] = waiter\n\t}\n\treturn waiters, nil\n}\n\nfunc validateIssueVarchars(issue *types.Issue) error {\n\tfields := []currentVarchar{\n\t\t{\"issue id\", issue.ID, types.MaxFieldLen},\n\t\t{\"issue title\", issue.Title, currentTitleVarcharRunes},\n\t\t{\"issue status\", string(issue.Status), currentShortVarcharRunes},\n\t\t{\"issue type\", string(issue.IssueType), currentShortVarcharRunes},\n\t\t{\"issue assignee\", issue.Assignee, types.MaxFieldLen},\n\t\t{\"issue created_by\", issue.CreatedBy, types.MaxFieldLen},\n\t\t{\"issue owner\", issue.Owner, types.MaxFieldLen},","sourceCodeStart":683,"sourceCodeEnd":719,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/migration/legacysqlite/reader.go#L683-L719","documentation":"decodeWaiters requires every element of the waiters JSON array to be a string. If element i is another JSON type (number, object, bool, null), it returns 'element %d is not a string' with the offending index. Each string is then further validated for UTF-8 via checkUTF8.","triggerScenarios":"A waiters array like '[1, \"alice\"]' or '[null]' is passed to decodeWaiters; the first non-string element aborts with its index.","commonSituations":"Waiter entries stored as IDs/numbers in an older legacy format, nulls from partial exports, or hand-edited JSON.","solutions":["Convert non-string waiter elements to strings in the legacy row.","Filter out null/non-string entries before migrating.","Fix the legacy exporter to always emit string waiter identifiers."],"exampleFix":"// before\n[42, \"alice\"]\n// after\n[\"42\", \"alice\"]","handlingStrategy":"type-guard","validationCode":"for i, el := range arr { if _, ok := el.(string); !ok { return fmt.Errorf(\"waiters[%d] is not a string\", i) } }","typeGuard":"func allStrings(v any) bool { arr, ok := v.([]any); if !ok { return false }; for _, el := range arr { if _, ok := el.(string); !ok { return false } }; return true }","tryCatchPattern":"waiters, err := decodeWaiters(raw); if err != nil { log.Warn(\"bad waiter element, coercing\", err); waiters = coerceToStrings(raw) }","preventionTips":["Enforce string IDs for waiters at write time","Reject non-string JSON types in the exporter","Validate legacy arrays element-by-element before migration"],"tags":["json","waiters","migration","type-mismatch"],"backgroundTag":"json-type-mismatch","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}