{"record":{"id":"9643b59defc6cd5d","repo":"gastownhall/beads","slug":"issue-s-waiters-w","errorCode":null,"errorMessage":"issue %s waiters: %w","messagePattern":"issue (.+?) waiters: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/migration/legacysqlite/reader.go","lineNumber":525,"sourceCode":"// metadata is stored verbatim unless it is the empty object; waiters is decoded.\nfunc (x legacyExtras) applyMetadataAndWaiters(issue *types.Issue) error {\n\tif x.metadata.Valid && x.metadata.String != \"\" && !json.Valid([]byte(x.metadata.String)) {\n\t\treturn fmt.Errorf(\"legacy SQLite issue %s has invalid metadata JSON\", issue.ID)\n\t}\n\tif x.metadata.Valid && x.metadata.String != \"\" {\n\t\tif err := checkJSONSurrogates(x.metadata.String); err != nil {\n\t\t\treturn fmt.Errorf(\"legacy SQLite issue %s metadata: %w\", issue.ID, err)\n\t\t}\n\t}\n\tif x.metadata.Valid && x.metadata.String != \"\" && x.metadata.String != \"{}\" {\n\t\tissue.Metadata = []byte(x.metadata.String)\n\t}\n\tif x.waiters.Valid && x.waiters.String != \"\" {\n\t\tif !json.Valid([]byte(x.waiters.String)) {\n\t\t\treturn fmt.Errorf(\"issue %s waiters: invalid JSON\", issue.ID)\n\t\t}\n\t\tif err := checkJSONSurrogates(x.waiters.String); err != nil {\n\t\t\treturn fmt.Errorf(\"issue %s waiters: %w\", issue.ID, err)\n\t\t}\n\t\twaiters, err := decodeWaiters(x.waiters.String)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"issue %s waiters: %w\", issue.ID, err)\n\t\t}\n\t\tissue.Waiters = waiters\n\t}\n\treturn nil\n}\n\n// applyCanonicalTimestamps normalizes the required created_at/updated_at values\n// to the canonical current-schema representation.\nfunc (x legacyExtras) applyCanonicalTimestamps(issue *types.Issue) error {\n\tvar err error\n\tif issue.CreatedAt, err = canonicalCurrentDatetime(issue.CreatedAt); err != nil {\n\t\treturn fmt.Errorf(\"legacy SQLite issue %s created_at: %w\", issue.ID, err)\n\t}\n\tif issue.UpdatedAt, err = canonicalCurrentDatetime(issue.UpdatedAt); err != nil {","sourceCodeStart":507,"sourceCodeEnd":543,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/migration/legacysqlite/reader.go#L507-L543","documentation":"During migration from a legacy SQLite database, the reader validates the `waiters` JSON column of each issue. This error wraps a failure from either checkJSONSurrogates (invalid Unicode surrogate pairs) or decodeWaiters (JSON that is valid but does not decode into the expected waiters structure). It means the stored waiters value cannot be safely carried into the current schema.","triggerScenarios":"Running `bd migrate` (or the legacy-SQLite validation path) against a legacy database where an issue's waiters column holds JSON with lone/unpaired Unicode surrogates, or structurally valid JSON whose shape does not match the expected waiters array (e.g. an object or string instead of a list of waiter entries).","commonSituations":"Databases written by older beads versions or third-party tools that stored hand-edited or truncated JSON in the waiters column; data produced on platforms that allowed invalid surrogate escapes (\\uD800 without a pair) to be written verbatim.","solutions":["Open the legacy SQLite DB and locate the offending issue ID shown in the message, then inspect its waiters column: sqlite3 legacy.db \"SELECT id, waiters FROM issues WHERE id='<id>'\"","Fix or clear the waiters value (set it to NULL or valid JSON, e.g. '[]') for that row","Re-encode any surrogate-escaped strings to proper UTF-8 before importing (e.g. re-dump the DB through a tool that normalizes text)","Re-run the migration"],"exampleFix":"// before (invalid: lone surrogate)\n{\"waiters\": \"[{\\\"name\\\": \\\"\\ud83d\\\"}]\"}\n// after (valid UTF-8, correct shape)\n{\"waiters\": \"[]\"}","handlingStrategy":"validation","validationCode":"import json\ndef validate_waiters(raw):\n    if raw is None or raw == '':\n        return True\n    try:\n        json.loads(raw)\n        return True\n    except (json.JSONDecodeError, ValueError):\n        return False\n# run against every row before migrating:\n# bad = [row['id'] for row in rows if not validate_waiters(row['waiters'])]","typeGuard":"def is_valid_waiters(raw) -> bool:\n    if not raw:\n        return True\n    if not isinstance(raw, str) or not json_valid(raw):\n        return False\n    v = json.loads(raw)\n    return isinstance(v, list)","tryCatchPattern":null,"preventionTips":["Validate all JSON columns with a dry-run script before migrating a legacy DB","Never hand-edit JSON columns; use the bd CLI","Normalize text columns to strict UTF-8 on export","Keep legacy DB backups so bad rows can be fixed and retried"],"tags":["migration","sqlite","json","data-integrity"],"backgroundTag":"invalid-json-column","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}