gastownhall/beads · error

multiple legacy dependencies for %s -> %s

Error message

multiple legacy dependencies for %s -> %s

What it means

Thrown by the legacy SQLite migration reader while importing dependencies for an issue. The reader tracks every (issue, depends-on) pair it has seen in a `seenDeps` map keyed by `id\x00to`; hitting the same pair again means the legacy database contains a duplicate dependency row for the same ordered pair. Rather than silently deduplicating, migration fails loudly so the source data can be cleaned.

Source

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

		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)
	}
	if !types.DependencyType(typ).IsValid() {
		return fmt.Errorf("dependency %s -> %s has invalid type", id, to)
	}
	d := &types.Dependency{IssueID: id, DependsOnID: to, Type: types.DependencyType(typ), CreatedAt: created, CreatedBy: by, Metadata: nullString(metadata), ThreadID: nullString(thread)}
	issue.Dependencies = append(issue.Dependencies, d)
	return nil

View on GitHub (pinned to 71377f2769)

Solutions

  1. Open the legacy SQLite DB and remove duplicate dependency rows, keeping one per (issue_id, depends_on_id): SELECT * FROM dependencies WHERE rowid NOT IN (SELECT MIN(rowid) FROM dependencies GROUP BY issue_id, depends_on_id)
  2. Add a UNIQUE(issue_id, depends_on_id) index to the legacy DB to confirm and prevent duplicates
  3. Re-export/re-generate the legacy database from a clean source before migrating

Example fix

-- before (duplicates in legacy deps)
-- bd-1 -> bd-2 (blocks), bd-1 -> bd-2 (blocks)
DELETE FROM dependencies WHERE rowid NOT IN (
  SELECT MIN(rowid) FROM dependencies GROUP BY issue_id, depends_on_id);
-- after: one row per pair
Defensive patterns

Strategy: validation

Validate before calling

const seen = new Set();
for (const d of legacyDeps) {
  const key = `${d.issue_id}\u0000${d.depends_on_id}`;
  if (seen.has(key)) throw new Error(`duplicate dependency ${d.issue_id} -> ${d.depends_on_id}`);
  seen.add(key);
}

Type guard

function isUniqueDeps(deps) {
  const keys = deps.map(d => `${d.issue_id}\u0000${d.depends_on_id}`);
  return new Set(keys).size === keys.length;
}

Try / catch

try { migrateLegacySQLite(dbPath) } catch (e) {
  if (e.message.includes('multiple legacy dependencies')) {
    dedupeLegacyDeps(dbPath); // DELETE dup rows group by issue_id, depends_on_id
    retry();
  } else throw e;
}

Prevention

When it happens

Trigger: Importing a legacy SQLite database whose `dependencies` table contains two or more rows with the same (issue_id, depends_on_id) pair (any type/created_at values); the dedup key is only the pair, so duplicates differing only in type or timestamp still collide.

Common situations: Legacy DBs corrupted by repeated import scripts or crash-repaired migration runs; old bd versions that lacked a UNIQUE(issue_id, depends_on_id) constraint; manual SQL edits or merges of two legacy databases.

Related errors


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