{"record":{"id":"c4689de8eac610e1","repo":"gastownhall/beads","slug":"w-count-edges-type-d-is-not-a-usable-dependency","errorCode":null,"errorMessage":"%w: count edges type %d is not a usable dependency type (non-empty, max %d chars)","messagePattern":"%w: count edges type (.+?) is not a usable dependency type \\(non-empty, max (.+?) chars\\)","errorType":"validation","errorClass":"storage.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/edge_counts.go","lineNumber":56,"sourceCode":"\tcase \"\":\n\t\treturn fmt.Errorf(\"%w: count edges requires a direction (%q or %q)\",\n\t\t\tstorage.ErrValidation, publicops.EdgeDirectionOut, publicops.EdgeDirectionIn)\n\tdefault:\n\t\treturn fmt.Errorf(\"%w: count edges direction %q is not %q or %q\",\n\t\t\tstorage.ErrValidation, request.Direction, publicops.EdgeDirectionOut, publicops.EdgeDirectionIn)\n\t}\n\tif request.Status != \"\" && request.Direction != publicops.EdgeDirectionIn {\n\t\treturn fmt.Errorf(\"%w: count edges status %q needs direction %q: an outbound edge's far end may be a row this database does not hold\",\n\t\t\tstorage.ErrValidation, request.Status, publicops.EdgeDirectionIn)\n\t}\n\tfor i, id := range request.IDs {\n\t\tif id == \"\" {\n\t\t\treturn fmt.Errorf(\"%w: count edges id %d is empty\", storage.ErrValidation, i)\n\t\t}\n\t}\n\tfor i, depType := range request.Types {\n\t\tif !depType.IsValid() {\n\t\t\treturn fmt.Errorf(\"%w: count edges type %d is not a usable dependency type (non-empty, max %d chars)\",\n\t\t\t\tstorage.ErrValidation, i, types.MaxDependencyTypeLen)\n\t\t}\n\t}\n\treturn nil\n}\n\n// FinishEdgeCount assembles the per-anchor answer from the two things every\n// implementation reads: which anchors exist, and the edge tallies keyed by\n// anchor.\n//\n// It is a pure function beside the body for the reason the checklist gives: the\n// parts that decide what the answer MEANS are pinned in milliseconds without a\n// database, and the conformance contract is left to assert what only a real\n// backend can show. What it decides here is the whole of the missing-anchor\n// rule — a missing anchor counts 0 whatever rows are keyed to it, and a present\n// anchor with no matching edges counts 0 too, which are the two facts a caller\n// tells apart by Missing and by nothing else.\nfunc FinishEdgeCount(anchors []string, present map[string]struct{}, tallies map[string]int64) publicops.EdgeCountResult {","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/edge_counts.go#L38-L74","documentation":"ValidateEdgeCountRequest rejects an EdgeCountRequest whose Types slice contains a dependency type that fails types.DependencyType.IsValid() — empty or longer than types.MaxDependencyTypeLen. An unusable type would act as a filter that silently matches nothing, so it is refused up front. The error wraps storage.ErrValidation.","triggerScenarios":"CountEdges / ExecuteEdgeCount called with Types: []types.DependencyType{\"\"} or a type string exceeding MaxDependencyTypeLen; constructing a type via types.DependencyType(rawUserInput) without validation.","commonSituations":"Free-form user input passed through as a dependency type; a typo like \"relates-to \" (trailing space inflating length); config or flags supplying a type renamed in a newer beads version; truncation or copying adding whitespace.","solutions":["Validate each type with types.DependencyType.IsValid() before building the request and drop/repair bad entries.","Normalize input: strings.TrimSpace and enforce the MaxDependencyTypeLen limit at the boundary.","Use the predefined dependency-type constants (e.g. types.DepBlocks, types.DepRelated) instead of raw strings.","If a version change renamed types, update the callers/config to the current names."],"exampleFix":"// before\nreq := publicops.EdgeCountRequest{IDs: ids, Direction: \"in\", Types: []types.DependencyType{userType}}\n// after\nuserType = types.DependencyType(strings.TrimSpace(string(userType)))\nif userType != \"\" && userType.IsValid() {\n\treq := publicops.EdgeCountRequest{IDs: ids, Direction: \"in\", Types: []types.DependencyType{userType}}\n}","handlingStrategy":"validation","validationCode":"func validTypes(ts []types.DependencyType) bool {\n\tfor _, t := range ts { if !t.IsValid() { return false } }\n\treturn true\n}","typeGuard":"func filterValidTypes(ts []types.DependencyType) []types.DependencyType {\n\treturn slices.DeleteFunc(slices.Clone(ts), func(t types.DependencyType) bool { return !t.IsValid() })\n}","tryCatchPattern":"if err := ValidateEdgeCountRequest(req); err != nil {\n\tif errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), \"dependency type\") {\n\t\t// sanitize types and rebuild the request\n\t}\n}","preventionTips":["Validate each type with DependencyType.IsValid() before building requests","TrimSpace user-supplied type strings","Use exported dependency-type constants, not raw strings","Re-check type names after beads upgrades"],"tags":["validation","dependency-type","edge-count"],"backgroundTag":"invalid-request-validation","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}