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 nilView on GitHub (pinned to 71377f2769)
Solutions
- 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)
- Add a UNIQUE(issue_id, depends_on_id) index to the legacy DB to confirm and prevent duplicates
- 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
- Add a UNIQUE(issue_id, depends_on_id) index to the legacy DB before migrating
- Deduplicate the dependencies table with GROUP BY before running import
- Never re-run legacy import scripts against an already-populated DB without dedup checks
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
- legacy SQLite comments %d and %d share current import identi
- sealed legacy SQLite database does not match source fingerpr
- sealed legacy SQLite WAL does not match source fingerprint
- legacy SQLite source changed while sealing
- legacy SQLite source %q must not be a symlink
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/9e55a76aba4a24de.
Report an issue: GitHub.