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

  1. Delete the orphaned label rows before migrating: DELETE FROM labels WHERE issue_id NOT IN (SELECT id FROM issues).
  2. If the label should survive, fix the issue_id to point at a real issue instead of deleting it.
  3. Run a full orphan sweep over child tables (labels, dependencies, comments) with NOT IN subqueries to catch all referential breaks at once.
  4. 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

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


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