{"record":{"id":"08b88c21793ccc7a","repo":"gastownhall/beads","slug":"w-s-cannot-depend-on-itself","errorCode":null,"errorMessage":"%w: %s cannot depend on itself","messagePattern":"%w: (.+?) cannot depend on itself","errorType":"validation","errorClass":"ErrSelfDependency","httpStatus":null,"severity":"error","filePath":"internal/storage/domain/dependency.go","lineNumber":284,"sourceCode":"\nfunc (u *dependencyUseCaseImpl) AddWispDependency(ctx context.Context, dep *types.Dependency, actor string) error {\n\treturn u.add(ctx, dep, actor, true)\n}\n\nfunc (u *dependencyUseCaseImpl) add(ctx context.Context, dep *types.Dependency, actor string, useWisp bool) error {\n\tif dep == nil {\n\t\treturn fmt.Errorf(\"add dep: dep must not be nil\")\n\t}\n\tif dep.IssueID == \"\" || dep.DependsOnID == \"\" {\n\t\treturn fmt.Errorf(\"add dep: IssueID and DependsOnID must be non-empty\")\n\t}\n\n\t// Self-dependency guard mirrors issueops.CheckDependencyCycleInTx: it is\n\t// checked BEFORE the cycle probe and for ALL dep types, and emits the\n\t// dedicated self-dep message. A blocking self-edge otherwise trips HasCycle\n\t// and would report the wrong (cycle) error (#4547 F-1).\n\tif dep.IssueID == dep.DependsOnID {\n\t\treturn fmt.Errorf(\"%w: %s cannot depend on itself\", ErrSelfDependency, dep.IssueID)\n\t}\n\tif err := u.depRepo.ValidateBlockingHierarchy(ctx, dep); err != nil {\n\t\tvar hierarchyConflict *DependencyHierarchyConflictError\n\t\tif errors.As(err, &hierarchyConflict) {\n\t\t\treturn err\n\t\t}\n\t\treturn fmt.Errorf(\"add dep: hierarchy check: %w\", err)\n\t}\n\n\tif types.IsSchedulingEdge(dep.Type) {\n\t\tcycle, err := u.depRepo.HasCycle(ctx, dep.IssueID, dep.DependsOnID)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"add dep: cycle check: %w\", err)\n\t\t}\n\t\tif cycle {\n\t\t\t// Match the embedded store's user-facing wording verbatim (no ids\n\t\t\t// prefix) so gc code that string-matches this error behaves the same\n\t\t\t// on both plumbings (#4547 F-1).","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/dependency.go#L266-L302","documentation":"A self-dependency rejection: the dependency being added has IssueID equal to DependsOnID, so an issue/wisp would depend on itself. It wraps the sentinel ErrSelfDependency so callers can detect this case with errors.Is. This check runs before the cycle probe because a blocking self-edge would otherwise be misreported as a generic cycle error (#4547 F-1).","triggerScenarios":"Calling AddDependency or AddWispDependency where dep.IssueID == dep.DependsOnID — typically the same ID variable passed to both fields, or a UI/script echoing one ID into both slots.","commonSituations":"Shell script reusing $ISSUE_ID for both arguments; form/UI pre-filling the 'depends on' field with the current issue; data import mapping the source column to both endpoints.","solutions":["Ensure the DependsOnID points to a different issue than IssueID","Detect the case programmatically with errors.Is(err, ErrSelfDependency) and surface a friendly message","Fix scripts/UI to use distinct source and target IDs","Sanitize imports to drop self-referencing edges"],"exampleFix":"// before\ndep := &types.Dependency{IssueID: id, DependsOnID: id} // self-dep\nerr := uc.AddDependency(ctx, dep, actor)\n// after\nif id == targetID { return fmt.Errorf(\"cannot block %s with itself\", id) }\ndep := &types.Dependency{IssueID: id, DependsOnID: targetID}\nerr := uc.AddDependency(ctx, dep, actor)","handlingStrategy":"validation","validationCode":"if issueID == dependsOnID {\n    return fmt.Errorf(\"%s cannot depend on itself\", issueID)\n}","typeGuard":"func isSelfDependency(err error) bool {\n    return errors.Is(err, storage.ErrSelfDependency)\n}","tryCatchPattern":"err := uc.AddDependency(ctx, dep, actor)\nif err != nil {\n    if errors.Is(err, storage.ErrSelfDependency) {\n        return fmt.Errorf(\"friendly message: %s cannot block itself\", dep.IssueID)\n    }\n    return err\n}","preventionTips":["Use distinct variables for source and target IDs in scripts","Guard UIs so the 'depends on' field cannot equal the current issue","Filter self-edges during data imports"],"tags":["dependency","validation","self-reference","sentinel-error"],"backgroundTag":"self-dependency","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}