gastownhall/beads · error
orphan label for %s
Error message
orphan label for %s
What it means
While loading labels during legacy migration, a labels row references an issue_id that has no corresponding issue in the loaded set (byID lookup returned nil). The reader treats referential integrity violations as fatal import errors rather than silently dropping orphaned labels, since the legacy database is expected to be consistent.
Source
Thrown at internal/migration/legacysqlite/reader.go:852
var id, label string
if err := labels.Scan(&id, &label); err != nil {
return err
}
if err := checkUTF8(
currentString{"label issue_id", id},
currentString{"label", label},
); err != nil {
return err
}
if err := checkCurrentVarchars(
currentVarchar{"label issue_id", id, types.MaxFieldLen},
currentVarchar{"label", label, types.MaxFieldLen},
); err != nil {
return err
}
issue := byID[id]
if issue == nil {
return fmt.Errorf("orphan label for %s", id)
}
issue.Labels = append(issue.Labels, label)
}
return labels.Err()
}
func loadDependencies(ctx context.Context, db *sql.Tx, byID map[string]*types.Issue) error {
deps, err := db.QueryContext(ctx, "SELECT issue_id,depends_on_id,type,CAST(created_at AS TEXT),created_by,metadata,thread_id FROM dependencies ORDER BY issue_id,depends_on_id,type")
if err != nil {
return err
}
defer deps.Close()
seenDeps := make(map[string]bool)
for deps.Next() {
if err := appendLegacyDependencyRow(deps, byID, seenDeps); err != nil {
return err
}
}View on GitHub (pinned to 71377f2769)
Solutions
- Delete the orphaned label rows before migrating: DELETE FROM labels WHERE issue_id NOT IN (SELECT id FROM issues).
- If the label should survive, fix the issue_id to point at a real issue instead of deleting it.
- Run a full orphan sweep over child tables (labels, dependencies, comments) with NOT IN subqueries to catch all referential breaks at once.
- Verify PRAGMA foreign_key_check on the legacy DB to surface all integrity violations before migration.
Example fix
// before: labels referencing deleted issues DELETE FROM labels WHERE issue_id NOT IN (SELECT id FROM issues); // after: no orphan labels remain
Defensive patterns
Strategy: validation
Validate before calling
// before migration, ensure referential integrity rows, err := legacyDB.Query(`SELECT COUNT(*) FROM labels WHERE issue_id NOT IN (SELECT id FROM issues)`) // if count > 0, delete or repair orphans first
Try / catch
if err := migrateLegacy(db); err != nil {
if strings.Contains(err.Error(), "orphan label for") {
// id is in the message; remove that labels row and retry
return fmt.Errorf("clean orphan labels and retry: %w", err)
}
return err
} Prevention
- Always delete child rows (labels, dependencies, comments) when deleting issues, even if FKs are off.
- Run PRAGMA foreign_key_check on the legacy DB before migrating.
- Avoid direct SQL deletes on the legacy database; use bd commands that maintain integrity.
When it happens
Trigger: The legacy SQLite labels table contains a row whose issue_id does not exist in the issues table — e.g. the issue was deleted without deleting its labels, or labels were inserted for an issue ID that was later changed. Raised in loadLabels after id/label pass UTF-8 and VARCHAR checks.
Common situations: Legacy databases from old bd versions that lacked foreign-key enforcement; manual DELETE FROM issues without cleaning labels; interrupted transactions that removed issues but not child rows; direct SQLite manipulation.
Related errors
- issue %s waiters: %w
- legacy SQLite issue has empty ID
- legacy SQLite issue %s has invalid created_at or updated_at
- dependency created_by is empty for %s -> %s
- orphan dependency %s -> %s
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/d4264ce7ce6c5268.
Report an issue: GitHub.