gastownhall/beads · error
dependency %s -> %s is a self-dependency
Error message
dependency %s -> %s is a self-dependency
What it means
After loading all issues, the reader validates the dependency graph and rejects any dependency whose issue and depends-on IDs are the same. Self-dependencies are meaningless in bd's graph and would poison cycle/hierarchy checks, so migration aborts with the offending pair.
Source
Thrown at internal/migration/legacysqlite/reader.go:994
}
identity := commentIdentity{issueID: issueID, author: author, text: text, createdAt: created}
if priorID, exists := seenComments[identity]; exists {
return fmt.Errorf("legacy SQLite comments %d and %d share current import identity", priorID, id)
}
seenComments[identity] = id
issue.Comments = append(issue.Comments, &types.Comment{ID: strconv.FormatInt(id, 10), IssueID: issueID, Author: author, Text: text, CreatedAt: created})
}
return comments.Err()
}
func validateDependencyGraph(issues []*types.Issue) error {
scheduling := make(map[string][]string)
hierarchy := make(map[string][]string)
var blocking []*types.Dependency
for _, issue := range issues {
for _, dep := range issue.Dependencies {
if dep.IssueID == dep.DependsOnID {
return fmt.Errorf("dependency %s -> %s is a self-dependency", dep.IssueID, dep.DependsOnID)
}
switch dep.Type {
case types.DepBlocks, types.DepConditionalBlocks:
blocking = append(blocking, dep)
scheduling[dep.IssueID] = append(scheduling[dep.IssueID], dep.DependsOnID)
case types.DepParentChild:
hierarchy[dep.IssueID] = append(hierarchy[dep.IssueID], dep.DependsOnID)
scheduling[dep.IssueID] = append(scheduling[dep.IssueID], dep.DependsOnID)
}
}
}
if hasDirectedCycle(scheduling) {
return fmt.Errorf("legacy SQLite dependency graph has a scheduling cycle")
}
for _, dep := range blocking {
if types.ExtractPrefix(dep.IssueID) == types.ExtractPrefix(dep.DependsOnID) &&
(reachable(hierarchy, dep.IssueID, dep.DependsOnID) ||
reachable(hierarchy, dep.DependsOnID, dep.IssueID)) {View on GitHub (pinned to 71377f2769)
Solutions
- Delete self-dependency rows in the legacy DB: DELETE FROM dependencies WHERE issue_id = depends_on_id
- Audit for other bad rows at the same time: SELECT * FROM dependencies WHERE issue_id = depends_on_id
- Re-run migration; recreate any intended link correctly (a self link has no valid replacement)
Example fix
-- before -- row: bd-1 -> bd-1 DELETE FROM dependencies WHERE issue_id = depends_on_id; -- after: no self-dependencies remain
Defensive patterns
Strategy: validation
Validate before calling
for (const d of legacyDeps) {
if (d.issue_id === d.depends_on_id)
throw new Error(`self-dependency ${d.issue_id} -> ${d.depends_on_id}`);
} Type guard
function hasNoSelfDeps(deps) {
return deps.every(d => d.issue_id !== d.depends_on_id);
} Try / catch
try { migrateLegacySQLite(dbPath) } catch (e) {
if (e.message.includes('is a self-dependency')) {
deleteSelfDependencies(dbPath); // WHERE issue_id = depends_on_id
retry();
} else throw e;
} Prevention
- DELETE rows WHERE issue_id = depends_on_id as a pre-migration cleanup step
- Add a CHECK (issue_id <> depends_on_id) constraint to the legacy schema
- Audit any custom tooling that writes dependency rows for self-link bugs
When it happens
Trigger: Legacy SQLite dependency row where issue_id equals depends_on_id (id -> id); detected during the per-issue sweep over issue.Dependencies.
Common situations: Buggy legacy tooling that let users or scripts depend an issue on itself; manual SQL inserts; restore bugs that rewrote one side of a dependency to the same ID.
Related errors
- legacy SQLite dependency graph has a scheduling cycle
- 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/bb9cccd1c77e112d.
Report an issue: GitHub.