{"record":{"id":"9194f6935255e572","repo":"gastownhall/beads","slug":"db-dependencysqlrepository-insert-dependsonid-mu","errorCode":null,"errorMessage":"db: DependencySQLRepository.Insert: DependsOnID must not be empty","messagePattern":"db: DependencySQLRepository\\.Insert: DependsOnID must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/dependency.go","lineNumber":79,"sourceCode":"\t\treturn \"depends_on_wisp_id\", nil\n\tcase errors.Is(err, sql.ErrNoRows):\n\t\treturn \"depends_on_issue_id\", nil\n\tcase dberrors.IsTableNotExist(err):\n\t\treturn \"depends_on_issue_id\", nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"classify dep target %s: %w\", dependsOnID, err)\n\t}\n}\n\nfunc (r *dependencySQLRepositoryImpl) Insert(ctx context.Context, dep *types.Dependency, actor string, opts domain.DepInsertOpts) error {\n\tif dep == nil {\n\t\treturn errors.New(\"db: DependencySQLRepository.Insert: dep must not be nil\")\n\t}\n\tif dep.IssueID == \"\" {\n\t\treturn errors.New(\"db: DependencySQLRepository.Insert: IssueID must not be empty\")\n\t}\n\tif dep.DependsOnID == \"\" {\n\t\treturn errors.New(\"db: DependencySQLRepository.Insert: DependsOnID must not be empty\")\n\t}\n\tif dep.IssueID == dep.DependsOnID {\n\t\t// Lead with the sentinel so this defensive repo-layer guard renders like\n\t\t// every other self-dep site (\"cannot add self-dependency: X cannot depend\n\t\t// on itself\") instead of appending the sentinel text.\n\t\treturn fmt.Errorf(\"db: DependencySQLRepository.Insert: %w: %s cannot depend on itself\", domain.ErrSelfDependency, dep.IssueID)\n\t}\n\n\tmetadata := dep.Metadata\n\tif metadata == \"\" {\n\t\tmetadata = \"{}\"\n\t}\n\n\tif !opts.HierarchyValidated {\n\t\tif err := r.ValidateBlockingHierarchy(ctx, dep); err != nil {\n\t\t\treturn err\n\t\t}\n\t}","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/dependency.go#L61-L97","documentation":"DependencySQLRepository.Insert returns this error when dep.DependsOnID is empty. A dependency must name the issue it depends on; an empty DependsOnID is rejected before any SQL executes. It is part of Insert's defensive validation chain (nil → IssueID → DependsOnID → self-dependency).","triggerScenarios":"Calling Insert with a *types.Dependency whose DependsOnID was never set — e.g. building the dependency from the owning issue's side only, or empty input from CLI/JSON where the target was omitted.","commonSituations":"CLI command invoked without the target issue argument; deserialization dropping the DependsOnID field; tests constructing minimal Dependency structs; fields swapped between IssueID and DependsOnID.","solutions":["Set dep.DependsOnID to the target issue's ID before calling Insert.","Fix the CLI/input path so the target ID is always captured and validated early.","Check for swapped IssueID/DependsOnID assignments."],"exampleFix":"// before\ndep := &types.Dependency{IssueID: parent.ID}\nrepo.Insert(ctx, dep, actor, opts)\n// after\ndep := &types.Dependency{IssueID: parent.ID, DependsOnID: target.ID}\nrepo.Insert(ctx, dep, actor, opts)","handlingStrategy":"validation","validationCode":"if dep == nil || dep.DependsOnID == \"\" {\n    return fmt.Errorf(\"DependsOnID required\")\n}","typeGuard":null,"tryCatchPattern":"if err := repo.Insert(ctx, dep, actor, opts); err != nil {\n    if strings.Contains(err.Error(), \"DependsOnID must not be empty\") {\n        // populate DependsOnID and retry\n    }\n}","preventionTips":["Require the target issue argument in CLI input parsing","Validate both IDs at the boundary before repo calls","Use a constructor that requires IssueID and DependsOnID"],"tags":["validation","dependency","go","storage"],"backgroundTag":"missing-required-field","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}