{"record":{"id":"457469f8ea43c232","repo":"gastownhall/beads","slug":"remove-dep-s-s-w","errorCode":null,"errorMessage":"remove dep %s -> %s: %w","messagePattern":"remove dep (.+?) -> (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/dependency.go","lineNumber":357,"sourceCode":"//\n// It is the source-routed twin of AddDependencies, and exists for the same\n// reason: `bd dep remove` takes whatever id the caller names, and pinning the\n// removal to the durable table means failing to remove an edge whose source is\n// a wisp while reporting that it was never there (bd-yby99.17). The delete IS\n// the verdict, the way the store-backed body reads it off RemoveDependencyInTx\n// rather than from a separate lookup.\nfunc (u *dependencyUseCaseImpl) RemoveDependencyBySource(ctx context.Context, sourceID, dependsOnID, actor string) (bool, error) {\n\tif sourceID == \"\" || dependsOnID == \"\" {\n\t\treturn false, fmt.Errorf(\"remove dep: sourceID and dependsOnID must not be empty\")\n\t}\n\twispSources, err := u.depRepo.WispSourceIDs(ctx, []string{sourceID})\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"remove dep: classify source: %w\", err)\n\t}\n\t_, sourceIsWisp := wispSources[sourceID]\n\tres, err := u.depRepo.Delete(ctx, sourceID, dependsOnID, actor, DepInsertOpts{UseWispsTable: sourceIsWisp, EmitEvent: true})\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"remove dep %s -> %s: %w\", sourceID, dependsOnID, err)\n\t}\n\treturn res.Found, nil\n}\n\nfunc (u *dependencyUseCaseImpl) removeDep(ctx context.Context, sourceID, dependsOnID, actor string, useWisp bool) error {\n\tif sourceID == \"\" || dependsOnID == \"\" {\n\t\treturn fmt.Errorf(\"remove dep: sourceID and dependsOnID must not be empty\")\n\t}\n\tif _, err := u.depRepo.Delete(ctx, sourceID, dependsOnID, actor, DepInsertOpts{UseWispsTable: useWisp, EmitEvent: true}); err != nil {\n\t\treturn fmt.Errorf(\"remove dep %s -> %s: %w\", sourceID, dependsOnID, err)\n\t}\n\treturn nil\n}\n\nfunc (u *dependencyUseCaseImpl) Reparent(ctx context.Context, childID, newParentID, actor string) error {\n\treturn u.reparent(ctx, childID, newParentID, actor, false)\n}\n","sourceCodeStart":339,"sourceCodeEnd":375,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/dependency.go#L339-L375","documentation":"Wraps a failure of depRepo.Delete when RemoveDependencyBySource actually removes the edge (sourceID -> dependsOnID) from the table matching the source's plane (wisp or regular). The edge's existence is reported by the returned res.Found boolean, so this error always means the delete operation itself failed — e.g. a storage error — not that the edge was absent.","triggerScenarios":"RemoveDependencyBySource has classified the source and calls depRepo.Delete with UseWispsTable set accordingly; the delete fails due to DB connection loss, SQL constraint/lock error, Dolt transaction conflict, or context cancellation mid-write.","commonSituations":"Concurrent writers deleting/updating the same edge; Dolt lock conflict during sync; database went read-only or disk full; connection pool exhausted during large batch dep-removal scripts.","solutions":["Inspect the wrapped cause to identify the storage failure (lock, connection, constraint).","Retry the delete — it is idempotent; res.Found tells you whether the edge existed afterward.","Check for concurrent processes holding locks on the same issue rows and serialize the work.","Verify the backend is writable and healthy (bd doctor) before bulk-removal scripts."],"exampleFix":"// before\nremoved, err := uc.RemoveDependencyBySource(ctx, src, dst, actor)\nif err != nil {\n    return err\n}\n// after\nremoved, err := uc.RemoveDependencyBySource(ctx, src, dst, actor)\nif err != nil {\n    if isTransient(err) {\n        return retryRemove(src, dst, actor) // delete is idempotent\n    }\n    return err\n}","handlingStrategy":"try-catch","validationCode":"// Verify the edge exists first (optional; res.Found also reports it)\nrows, err := db.QueryContext(ctx,\n    `SELECT 1 FROM dependencies WHERE issue_id=? AND depends_on_id=?`, src, dst)\nif err == nil && !rows.Next() {\n    return nil // nothing to remove\n}","typeGuard":null,"tryCatchPattern":"found, err := uc.RemoveDependencyBySource(ctx, src, dst, actor)\nif err != nil {\n    if isTransientStorageErr(err) {\n        return retryRemove(src, dst, actor) // delete is idempotent\n    }\n    return fmt.Errorf(\"delete failed for %s -> %s: %w\", src, dst, err)\n}\nif !found {\n    log.Println(\"edge already absent\")\n}","preventionTips":["Treat deletes as idempotent and retry transient failures","Serialize concurrent removals touching the same issues","Check for Dolt lock conflicts during sync before bulk deletes","Use RemoveDependencyBySource (source-routed) to avoid plane mismatches"],"tags":["go","storage","delete","dependency-graph"],"backgroundTag":"dependency-delete-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}