{"record":{"id":"0a0f50cd4666bece","repo":"gastownhall/beads","slug":"w-add-dependencies-edge-d-requires-both-endpoin","errorCode":null,"errorMessage":"%w: add dependencies edge %d requires both endpoints","messagePattern":"%w: add dependencies edge (.+?) requires both endpoints","errorType":"validation","errorClass":"storage.ErrValidation","httpStatus":null,"severity":"warning","filePath":"internal/storage/issueops/dependency_editor.go","lineNumber":37,"sourceCode":"// The self-dependency refusal is here, ahead of the per-edge cycle probe and\n// for EVERY edge type, for the reason the domain path states: a blocking\n// self-edge otherwise trips the cycle check and reports the wrong refusal, and\n// SkipPerEdgeCycleCheck would skip it entirely.\n//\n// The type check is that there IS a type — non-empty, within the column's\n// length. It is deliberately not a membership test: the vocabulary is an open,\n// workspace-configurable set (see the Dep* constants), so refusing an unlisted\n// type would refuse a workspace's own.\nfunc ValidateAddDependenciesRequest(request publicops.AddDependenciesRequest) error {\n\tif request.Actor == \"\" {\n\t\treturn fmt.Errorf(\"%w: add dependencies requires an actor\", storage.ErrValidation)\n\t}\n\tif len(request.Edges) == 0 {\n\t\treturn fmt.Errorf(\"%w: add dependencies requires at least one edge\", storage.ErrValidation)\n\t}\n\tfor i, edge := range request.Edges {\n\t\tif edge.IssueID == \"\" || edge.DependsOnID == \"\" {\n\t\t\treturn fmt.Errorf(\"%w: add dependencies edge %d requires both endpoints\", storage.ErrValidation, i)\n\t\t}\n\t\tif !edge.Type.IsValid() {\n\t\t\treturn fmt.Errorf(\"%w: add dependencies edge %d requires a dependency type (max %d chars)\",\n\t\t\t\tstorage.ErrValidation, i, types.MaxDependencyTypeLen)\n\t\t}\n\t\tif edge.IssueID == edge.DependsOnID {\n\t\t\treturn fmt.Errorf(\"%w: %s cannot depend on itself\", domain.ErrSelfDependency, edge.IssueID)\n\t\t}\n\t}\n\treturn nil\n}\n\n// ValidateRemoveDependencyRequest applies the request rules every\n// DependencyEditor implementation shares for a removal.\nfunc ValidateRemoveDependencyRequest(request publicops.RemoveDependencyRequest) error {\n\tif request.Actor == \"\" {\n\t\treturn fmt.Errorf(\"%w: remove dependency requires an actor\", storage.ErrValidation)\n\t}","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependency_editor.go#L19-L55","documentation":"Every dependency edge must have both endpoints set: the IssueID (the issue that depends) and the DependsOnID (the issue it depends on). When either is empty, the validator rejects the request with this message including the edge's index, wrapped with storage.ErrValidation. This catches malformed edges before they reach the database.","triggerScenarios":"Calling AddDependencies with an edge where Edge.IssueID == \"\" or Edge.DependsOnID == \"\" — e.g. parsing dependency output where one side of the pair was missing or a struct literal omitted a field.","commonSituations":"Importing dependencies from external data files with missing IDs; lookups that returned empty IDs for deleted/unknown issues; hand-written migrations building edges programmatically.","solutions":["Populate both IssueID and DependsOnID for every edge before calling.","Verify the referenced issues exist and their IDs were resolved correctly upstream.","Filter out edges with blank endpoints before constructing the request.","Use the reported index in the message to find the offending edge in your batch."],"exampleFix":"// before\nedges := []publicops.DependencyEdge{{IssueID: id}} // DependsOnID missing\n// after\nedges := []publicops.DependencyEdge{{IssueID: id, DependsOnID: depID, Type: types.DepBlocks}}","handlingStrategy":"validation","validationCode":"for i, e := range req.Edges {\n    if e.IssueID == \"\" || e.DependsOnID == \"\" {\n        return fmt.Errorf(\"edge %d missing endpoint\", i)\n    }\n}","typeGuard":"func edgeComplete(e publicops.DependencyEdge) bool {\n    return e.IssueID != \"\" && e.DependsOnID != \"\"\n}","tryCatchPattern":"err := store.AddDependencies(ctx, req)\nif errors.Is(err, storage.ErrValidation) {\n    // message names the offending edge index; drop/fix that edge and retry\n}","preventionTips":["Validate all edges before constructing the request.","Resolve issue IDs via lookup, never trust raw parsed input.","Drop edges with blank endpoints early with a visible warning."],"tags":["validation","dependencies","missing-id"],"backgroundTag":"missing-required-field","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}