{"record":{"id":"102241ceee498da5","repo":"gastownhall/beads","slug":"remove-dep-sourceid-and-dependsonid-must-not-be-e","errorCode":null,"errorMessage":"remove dep: sourceID and dependsOnID must not be empty","messagePattern":"remove dep: sourceID and dependsOnID must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/dependency.go","lineNumber":348,"sourceCode":"\treturn u.removeDep(ctx, issueID, dependsOnID, actor, false)\n}\n\nfunc (u *dependencyUseCaseImpl) RemoveWispDependency(ctx context.Context, wispID, dependsOnID, actor string) error {\n\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 {","sourceCodeStart":330,"sourceCodeEnd":366,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/dependency.go#L330-L366","documentation":"A guard error from RemoveDependencyBySource: one or both of the edge endpoint IDs (sourceID, dependsOnID) is the empty string. The use case cannot address, classify, or delete an edge without both endpoints, so it refuses before touching the repository. It is a caller-input validation failure, not a storage problem.","triggerScenarios":"Calling RemoveDependencyBySource(ctx, \"\", dependsOnID, actor) or with an empty dependsOnID — typically because an upstream lookup returned no ID, a parsed CLI argument was blank, or a struct field was never populated before the call.","commonSituations":"Scripting `bd dep remove` with a variable that failed to resolve; parsing issue IDs from output where a column was empty; constructing the call from JSON where a key was missing and Go zero-values the string.","solutions":["Check both IDs for emptiness at the call site before invoking RemoveDependencyBySource.","Trace where the empty ID comes from — fix the upstream lookup/parse so a real issue ID is produced.","If the edge may not exist, look up the issue first and skip the removal call rather than passing a blank ID.","Return a clear validation error to your user naming which ID was missing."],"exampleFix":"// before\nremoved, err := uc.RemoveDependencyBySource(ctx, issueID, depID, actor)\n// after\nif issueID == \"\" || depID == \"\" {\n    return fmt.Errorf(\"cannot remove dep: issueID=%q depID=%q\", issueID, depID)\n}\nremoved, err := uc.RemoveDependencyBySource(ctx, issueID, depID, actor)","handlingStrategy":"validation","validationCode":"func validateEdgeIDs(sourceID, dependsOnID string) error {\n    if sourceID == \"\" {\n        return fmt.Errorf(\"sourceID is empty\")\n    }\n    if dependsOnID == \"\" {\n        return fmt.Errorf(\"dependsOnID is empty\")\n    }\n    return nil\n}\nif err := validateEdgeIDs(sourceID, dependsOnID); err != nil {\n    return err\n}\nremoved, err := uc.RemoveDependencyBySource(ctx, sourceID, dependsOnID, actor)","typeGuard":"func hasEdgeIDs(sourceID, dependsOnID string) bool {\n    return sourceID != \"\" && dependsOnID != \"\"\n}","tryCatchPattern":"if err := validateEdgeIDs(sourceID, dependsOnID); err != nil {\n    return fmt.Errorf(\"cannot remove dep: %w\", err)\n}\n_, err := uc.RemoveDependencyBySource(ctx, sourceID, dependsOnID, actor)","preventionTips":["Never pass CLI/script variables without checking they resolved to a real ID","Validate struct fields are populated before calling removal APIs","In batch loops, skip entries with empty endpoint IDs and log them","Look up the issue first; if not found, skip removal instead of passing blank IDs"],"tags":["go","validation","dependency-graph","empty-argument"],"backgroundTag":"missing-required-argument","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}