{"record":{"id":"9e55a76aba4a24de","repo":"gastownhall/beads","slug":"multiple-legacy-dependencies-for-s-s","errorCode":null,"errorMessage":"multiple legacy dependencies for %s -> %s","messagePattern":"multiple legacy dependencies for (.+?) -> (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/migration/legacysqlite/reader.go","lineNumber":912,"sourceCode":"\t\tcurrentVarchar{\"dependency depends_on_id\", to, types.MaxFieldLen},\n\t\tcurrentVarchar{\"dependency type\", typ, currentShortVarcharRunes},\n\t\tcurrentVarchar{\"dependency created_by\", by, types.MaxFieldLen},\n\t); err != nil {\n\t\treturn err\n\t}\n\tif by == \"\" {\n\t\treturn fmt.Errorf(\"dependency created_by is empty for %s -> %s\", id, to)\n\t}\n\tissue := byID[id]\n\tif issue == nil || byID[to] == nil {\n\t\treturn fmt.Errorf(\"orphan dependency %s -> %s\", id, to)\n\t}\n\tif issue.Ephemeral != byID[to].Ephemeral {\n\t\treturn fmt.Errorf(\"dependency %s -> %s crosses ephemeral storage\", id, to)\n\t}\n\tkey := id + \"\\x00\" + to\n\tif seenDeps[key] {\n\t\treturn fmt.Errorf(\"multiple legacy dependencies for %s -> %s\", id, to)\n\t}\n\tseenDeps[key] = true\n\tcreated, e := parseTime(at)\n\tif e != nil {\n\t\treturn e\n\t}\n\tif created.IsZero() {\n\t\treturn fmt.Errorf(\"dependency created_at is zero for %s -> %s\", id, to)\n\t}\n\tif (metadata.Valid && metadata.String != \"\") || (thread.Valid && thread.String != \"\") {\n\t\treturn fmt.Errorf(\"dependency %s -> %s uses unsupported metadata or thread ID\", id, to)\n\t}\n\tif !types.DependencyType(typ).IsValid() {\n\t\treturn fmt.Errorf(\"dependency %s -> %s has invalid type\", id, to)\n\t}\n\td := &types.Dependency{IssueID: id, DependsOnID: to, Type: types.DependencyType(typ), CreatedAt: created, CreatedBy: by, Metadata: nullString(metadata), ThreadID: nullString(thread)}\n\tissue.Dependencies = append(issue.Dependencies, d)\n\treturn nil","sourceCodeStart":894,"sourceCodeEnd":930,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/migration/legacysqlite/reader.go#L894-L930","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"-- before (duplicates in legacy deps)\n-- bd-1 -> bd-2 (blocks), bd-1 -> bd-2 (blocks)\nDELETE FROM dependencies WHERE rowid NOT IN (\n  SELECT MIN(rowid) FROM dependencies GROUP BY issue_id, depends_on_id);\n-- after: one row per pair","handlingStrategy":"validation","validationCode":"const seen = new Set();\nfor (const d of legacyDeps) {\n  const key = `${d.issue_id}\\u0000${d.depends_on_id}`;\n  if (seen.has(key)) throw new Error(`duplicate dependency ${d.issue_id} -> ${d.depends_on_id}`);\n  seen.add(key);\n}","typeGuard":"function isUniqueDeps(deps) {\n  const keys = deps.map(d => `${d.issue_id}\\u0000${d.depends_on_id}`);\n  return new Set(keys).size === keys.length;\n}","tryCatchPattern":"try { migrateLegacySQLite(dbPath) } catch (e) {\n  if (e.message.includes('multiple legacy dependencies')) {\n    dedupeLegacyDeps(dbPath); // DELETE dup rows group by issue_id, depends_on_id\n    retry();\n  } else throw e;\n}","preventionTips":["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"],"tags":["migration","sqlite","duplicate-data"],"backgroundTag":"duplicate-dependency-row","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}