{"record":{"id":"e4dcd403d4e6417a","repo":"gastownhall/beads","slug":"remove-dep-classify-source-w","errorCode":null,"errorMessage":"remove dep: classify source: %w","messagePattern":"remove dep: classify source: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/dependency.go","lineNumber":352,"sourceCode":"\treturn u.removeDep(ctx, wispID, dependsOnID, actor, true)\n}\n\n// RemoveDependencyBySource removes one edge from the plane its SOURCE lives in\n// and reports whether there was an edge to remove.\n//\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}","sourceCodeStart":334,"sourceCodeEnd":370,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/dependency.go#L334-L370","documentation":"Wraps a failure of depRepo.WispSourceIDs while RemoveDependencyBySource classifies whether the edge's source issue lives in the wisps table or the regular table. The classification determines which table the delete targets; when the lookup itself fails, the removal is aborted with this wrapper so the caller knows the failure happened before any delete was attempted.","triggerScenarios":"RemoveDependencyBySource calls WispSourceIDs(ctx, []string{sourceID}) and the repo query fails — DB unreachable, SQL error, context canceled, or Dolt transaction/lock error while scanning the wisps classification.","commonSituations":"Database connection dropped mid-command; context deadline exceeded during batch removals; Dolt server error or schema mismatch after a version upgrade; read-only replica used for the classification query.","solutions":["Fix the underlying repository error surfaced by %w (connectivity, SQL, context).","Confirm the database schema is current (bd doctor / migration status) since wisp classification reads the wisps table.","Retry the removal once storage is healthy — no delete has run yet, so it is safe to re-invoke.","If timeouts are the cause, increase the context deadline for large batch scripts."],"exampleFix":"// before: assuming removal failed because the edge was missing\nremoved, err := uc.RemoveDependencyBySource(ctx, src, dst, actor)\nif err != nil {\n    log.Println(\"edge not found\")\n}\n// after: distinguish classification failure from not-found\nremoved, err := uc.RemoveDependencyBySource(ctx, src, dst, actor)\nif err != nil {\n    log.Printf(\"removal aborted before delete: %v\", err) // storage issue\n}","handlingStrategy":"try-catch","validationCode":"// Pre-verify storage reachability before batch removal\nif err := db.PingContext(ctx); err != nil {\n    return fmt.Errorf(\"storage unreachable, aborting removals: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"_, err := uc.RemoveDependencyBySource(ctx, src, dst, actor)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) {\n        return retryWithLongerDeadline()\n    }\n    return fmt.Errorf(\"classification/read failure, nothing deleted: %w\", err)\n}","preventionTips":["Use contexts with sufficient deadlines for batch scripts","Confirm schema/migrations are current so the wisps table is queryable","Retry safely: a classification failure means no delete occurred","Monitor DB connectivity before running large dep-removal jobs"],"tags":["go","storage","wisp","dependency-graph","classification"],"backgroundTag":"storage-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}