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

  1. Find and delete orphaned comments: DELETE FROM comments WHERE issue_id NOT IN (SELECT id FROM issues)
  2. Restore the missing parent issues in the legacy DB if the comments matter
  3. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/51284b3dd1198560. Report an issue: GitHub.