gastownhall/beads · error
orphan comment for %s
Error message
orphan comment for %s
What it means
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.
Source
Thrown at internal/migration/legacysqlite/reader.go:965
if err := comments.Scan(&id, &issueID, &author, &text, &at); err != nil {
return err
}
if err := checkUTF8(
currentString{"comment issue_id", issueID},
currentString{"comment author", author},
currentString{"comment text", text},
); err != nil {
return err
}
if err := checkCurrentVarchars(
currentVarchar{"comment issue_id", issueID, types.MaxFieldLen},
currentVarchar{"comment author", author, types.MaxFieldLen},
); err != nil {
return err
}
issue := byID[issueID]
if issue == nil {
return fmt.Errorf("orphan comment for %s", issueID)
}
if issue.Ephemeral && len(text) > currentTextBytes {
return fmt.Errorf("legacy SQLite ephemeral comment text is %d bytes (current TEXT maximum %d)", len(text), currentTextBytes)
}
created, e := parseTime(at)
if e != nil {
return e
}
if created.IsZero() {
return fmt.Errorf("comment created_at is zero for issue %s", issueID)
}
identity := commentIdentity{issueID: issueID, author: author, text: text, createdAt: created}
if priorID, exists := seenComments[identity]; exists {
return fmt.Errorf("legacy SQLite comments %d and %d share current import identity", priorID, id)
}
seenComments[identity] = id
issue.Comments = append(issue.Comments, &types.Comment{ID: strconv.FormatInt(id, 10), IssueID: issueID, Author: author, Text: text, CreatedAt: created})
}View on GitHub (pinned to 71377f2769)
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
Example fix
-- before -- comments reference bd-999 which is not in issues DELETE FROM comments WHERE issue_id NOT IN (SELECT id FROM issues); -- after: no orphan comments remain
Defensive patterns
Strategy: validation
Validate before calling
const issueIds = new Set(legacyIssues.map(i => i.id));
for (const c of legacyComments) {
if (!issueIds.has(c.issue_id))
throw new Error(`orphan comment for ${c.issue_id}`);
} Type guard
function commentHasParent(c, issueIds) {
return issueIds.has(c.issue_id);
} Try / catch
try { migrateLegacySQLite(dbPath) } catch (e) {
if (e.message.startsWith('orphan comment')) {
const issueID = e.message.match(/for (\S+)/)[1];
restoreOrDeleteIssue(dbPath, issueID);
retry();
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- orphan label for %s
- orphan dependency %s -> %s
- sealed legacy SQLite database does not match source fingerpr
- sealed legacy SQLite WAL does not match source fingerprint
- legacy SQLite source changed while sealing
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/51284b3dd1198560.
Report an issue: GitHub.