{"record":{"id":"51284b3dd1198560","repo":"gastownhall/beads","slug":"orphan-comment-for-s","errorCode":null,"errorMessage":"orphan comment for %s","messagePattern":"orphan comment for (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/migration/legacysqlite/reader.go","lineNumber":965,"sourceCode":"\t\tif err := comments.Scan(&id, &issueID, &author, &text, &at); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := checkUTF8(\n\t\t\tcurrentString{\"comment issue_id\", issueID},\n\t\t\tcurrentString{\"comment author\", author},\n\t\t\tcurrentString{\"comment text\", text},\n\t\t); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := checkCurrentVarchars(\n\t\t\tcurrentVarchar{\"comment issue_id\", issueID, types.MaxFieldLen},\n\t\t\tcurrentVarchar{\"comment author\", author, types.MaxFieldLen},\n\t\t); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tissue := byID[issueID]\n\t\tif issue == nil {\n\t\t\treturn fmt.Errorf(\"orphan comment for %s\", issueID)\n\t\t}\n\t\tif issue.Ephemeral && len(text) > currentTextBytes {\n\t\t\treturn fmt.Errorf(\"legacy SQLite ephemeral comment text is %d bytes (current TEXT maximum %d)\", len(text), currentTextBytes)\n\t\t}\n\t\tcreated, e := parseTime(at)\n\t\tif e != nil {\n\t\t\treturn e\n\t\t}\n\t\tif created.IsZero() {\n\t\t\treturn fmt.Errorf(\"comment created_at is zero for issue %s\", issueID)\n\t\t}\n\t\tidentity := commentIdentity{issueID: issueID, author: author, text: text, createdAt: created}\n\t\tif priorID, exists := seenComments[identity]; exists {\n\t\t\treturn fmt.Errorf(\"legacy SQLite comments %d and %d share current import identity\", priorID, id)\n\t\t}\n\t\tseenComments[identity] = id\n\t\tissue.Comments = append(issue.Comments, &types.Comment{ID: strconv.FormatInt(id, 10), IssueID: issueID, Author: author, Text: text, CreatedAt: created})\n\t}","sourceCodeStart":947,"sourceCodeEnd":983,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/migration/legacysqlite/reader.go#L947-L983","documentation":"While importing comments, the reader looks up the parent issue by ID in the already-loaded issue map; if no issue exists for the comment's issue_id the comment is an orphan and migration aborts. bd requires every comment to attach to a known issue in the same import set.","triggerScenarios":"Legacy SQLite comments table rows referencing an issue_id that is absent from the issues table, or that was skipped/filtered earlier in the same migration run (e.g. failed validation and never entered byID).","commonSituations":"Legacy databases with cascading deletes disabled so comments outlived their issues; import filters that drop some issues but not their comments; corrupted or manually pruned issues tables.","solutions":["Find and delete orphaned comments: DELETE FROM comments WHERE issue_id NOT IN (SELECT id FROM issues)","Restore the missing parent issues in the legacy DB if the comments matter","Check earlier migration logs for issues that failed validation and were skipped — fix them so they load and own the comments"],"exampleFix":"-- before\n-- comments reference bd-999 which is not in issues\nDELETE FROM comments WHERE issue_id NOT IN (SELECT id FROM issues);\n-- after: no orphan comments remain","handlingStrategy":"validation","validationCode":"const issueIds = new Set(legacyIssues.map(i => i.id));\nfor (const c of legacyComments) {\n  if (!issueIds.has(c.issue_id))\n    throw new Error(`orphan comment for ${c.issue_id}`);\n}","typeGuard":"function commentHasParent(c, issueIds) {\n  return issueIds.has(c.issue_id);\n}","tryCatchPattern":"try { migrateLegacySQLite(dbPath) } catch (e) {\n  if (e.message.startsWith('orphan comment')) {\n    const issueID = e.message.match(/for (\\S+)/)[1];\n    restoreOrDeleteIssue(dbPath, issueID);\n    retry();\n  } else throw e;\n}","preventionTips":["Enable ON DELETE CASCADE for comments in the legacy schema","Run an orphan check (comments NOT IN issues) before migration","Never filter/skip issues during import without handling their comments"],"tags":["migration","sqlite","referential-integrity"],"backgroundTag":"orphaned-foreign-key","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}