{"record":{"id":"6be24088ccc32ed3","repo":"gastownhall/beads","slug":"db-dependencysqlrepository-insert-issueid-must-n","errorCode":null,"errorMessage":"db: DependencySQLRepository.Insert: IssueID must not be empty","messagePattern":"db: DependencySQLRepository\\.Insert: IssueID must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/dependency.go","lineNumber":76,"sourceCode":"\terr := r.runner.QueryRowContext(ctx, \"SELECT 1 FROM wisps WHERE id = ? LIMIT 1\", dependsOnID).Scan(&probe)\n\tswitch {\n\tcase err == nil:\n\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 {","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/dependency.go#L58-L94","documentation":"DependencySQLRepository.Insert returns this error when dep.IssueID is empty. The dependency must record which issue owns the dependency, so an empty IssueID is rejected before any SQL executes. It is a defensive repo-layer guard in the same validation chain as the nil and DependsOnID checks.","triggerScenarios":"Calling Insert with a *types.Dependency whose IssueID field was never set — e.g. constructing the dependency from the 'depends-on' side only, or a partially filled struct from deserialization.","commonSituations":"Building dependencies from JSON/CLI input where the owning issue ID was omitted; swapping IssueID/DependsOnID by mistake; tests constructing minimal Dependency structs.","solutions":["Set dep.IssueID to the owning issue's ID before calling Insert.","Verify field order — if IssueID and DependsOnID were accidentally swapped, correct the assignment.","Add upstream validation (service/command layer) that both IDs are present."],"exampleFix":"// before\ndep := &types.Dependency{DependsOnID: target.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.IssueID == \"\" {\n    return fmt.Errorf(\"IssueID required\")\n}","typeGuard":null,"tryCatchPattern":"if err := repo.Insert(ctx, dep, actor, opts); err != nil {\n    if strings.Contains(err.Error(), \"IssueID must not be empty\") {\n        // populate IssueID and retry\n    }\n}","preventionTips":["Validate both IDs in the CLI/service layer before repo calls","Use a constructor that requires IssueID and DependsOnID","Watch for swapped IssueID/DependsOnID assignments"],"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"}