gastownhall/beads · error

orphan dependency %s -> %s

Error message

orphan dependency %s -> %s

What it means

A dependency row references an issue_id or depends_on_id that does not exist among the loaded issues (byID map). The reader refuses to import dangling dependency edges because they would violate the current database's foreign-key integrity and produce a broken dependency graph.

Source

Thrown at internal/migration/legacysqlite/reader.go:905

		currentString{"dependency type", typ},
		currentString{"dependency created_by", by},
	); err != nil {
		return err
	}
	if err := checkCurrentVarchars(
		currentVarchar{"dependency issue_id", id, types.MaxFieldLen},
		currentVarchar{"dependency depends_on_id", to, types.MaxFieldLen},
		currentVarchar{"dependency type", typ, currentShortVarcharRunes},
		currentVarchar{"dependency created_by", by, types.MaxFieldLen},
	); err != nil {
		return err
	}
	if by == "" {
		return fmt.Errorf("dependency created_by is empty for %s -> %s", id, to)
	}
	issue := byID[id]
	if issue == nil || byID[to] == nil {
		return fmt.Errorf("orphan dependency %s -> %s", id, to)
	}
	if issue.Ephemeral != byID[to].Ephemeral {
		return fmt.Errorf("dependency %s -> %s crosses ephemeral storage", id, to)
	}
	key := id + "\x00" + to
	if seenDeps[key] {
		return fmt.Errorf("multiple legacy dependencies for %s -> %s", id, to)
	}
	seenDeps[key] = true
	created, e := parseTime(at)
	if e != nil {
		return e
	}
	if created.IsZero() {
		return fmt.Errorf("dependency created_at is zero for %s -> %s", id, to)
	}
	if (metadata.Valid && metadata.String != "") || (thread.Valid && thread.String != "") {
		return fmt.Errorf("dependency %s -> %s uses unsupported metadata or thread ID", id, to)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Remove dangling edges before migrating: DELETE FROM dependencies WHERE issue_id NOT IN (SELECT id FROM issues) OR depends_on_id NOT IN (SELECT id FROM issues).
  2. If the edge is still meaningful, re-point the missing endpoint to the correct existing issue ID.
  3. Run PRAGMA foreign_key_check on the legacy DB to enumerate all integrity violations in one pass.
  4. Fix the deletion workflow that produced orphans (enable FK enforcement or always clean child rows) so it does not recur.

Example fix

// before: edges pointing at deleted issues
DELETE FROM dependencies WHERE issue_id NOT IN (SELECT id FROM issues) OR depends_on_id NOT IN (SELECT id FROM issues);
// after: all endpoints resolve
Defensive patterns

Strategy: validation

Validate before calling

rows, err := legacyDB.Query(`SELECT issue_id, depends_on_id FROM dependencies
  WHERE issue_id NOT IN (SELECT id FROM issues)
     OR depends_on_id NOT IN (SELECT id FROM issues)`)
// delete or re-point these dangling edges before migrating

Try / catch

if err := migrateLegacy(db); err != nil {
    if strings.Contains(err.Error(), "orphan dependency") {
        return fmt.Errorf("remove dangling dependency edges and retry: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: The legacy SQLite dependencies table contains an edge whose either endpoint was deleted (or renamed) without removing the edge, detected in appendLegacyDependencyRow when byID[id] or byID[to] is nil after the created_by check.

Common situations: Legacy versions without foreign-key enforcement deleting issues directly via SQL; bulk cleanup scripts that removed issues but left dependencies; corrupted databases from crashed old versions; test fixtures with hand-written inconsistent edges.

Related errors


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