{"record":{"id":"db0e2cbb203b0186","repo":"gastownhall/beads","slug":"w-count-edges-id-d-is-empty","errorCode":null,"errorMessage":"%w: count edges id %d is empty","messagePattern":"%w: count edges id (.+?) is empty","errorType":"validation","errorClass":"storage.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/edge_counts.go","lineNumber":51,"sourceCode":"// per-entry checks that follow tell a caller's mistake from a legitimately\n// empty answer, exactly as ValidateEdgeReadRequest's do.\nfunc ValidateEdgeCountRequest(request publicops.EdgeCountRequest) error {\n\tswitch request.Direction {\n\tcase publicops.EdgeDirectionIn, publicops.EdgeDirectionOut:\n\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","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/edge_counts.go#L33-L69","documentation":"ValidateEdgeCountRequest rejects an EdgeCountRequest whose IDs slice contains an empty string at position %d. An empty ID entry names no issue, so the library refuses rather than returning a misleading entry in the per-anchor result. It wraps storage.ErrValidation; an entirely empty IDs slice is legal and just yields an empty answer.","triggerScenarios":"Passing EdgeCountRequest{IDs: []string{\"bd-1\", \"\"}} to CountEdges / ExecuteEdgeCount; building the IDs slice from parsed CLI args or JSON where an empty token or blank field slips in.","commonSituations":"Splitting a comma-separated ID list with strings.Split and getting a trailing/empty element; a JSON array containing \"\"; a form or script variable left unset; map iteration producing an empty key.","solutions":["Remove empty strings from request.IDs before calling (filter with a small loop or slices.DeleteFunc).","Fix the producer: use strings.Fields / a filtering split instead of strings.Split for whitespace-delimited ID lists.","Validate user input at the CLI boundary and reject blank arguments early.","Skip empty entries explicitly if callers may legitimately pass sparse input."],"exampleFix":"// before\nids := strings.Split(flagValue, \",\") // may contain \"\"\nreq := publicops.EdgeCountRequest{IDs: ids, Direction: \"in\"}\n// after\nids := slices.Collect(strings.Fields(flagValue)) // or filter out \"\"\nreq := publicops.EdgeCountRequest{IDs: ids, Direction: \"in\"}","handlingStrategy":"validation","validationCode":"func hasEmptyID(ids []string) bool { return slices.Contains(ids, \"\") }","typeGuard":"func nonEmptyIDs(ids []string) []string {\n\treturn slices.DeleteFunc(slices.Clone(ids), func(s string) bool { return s == \"\" })\n}","tryCatchPattern":"if err := ExecuteEdgeCount(ctx, tx, req); err != nil {\n\tif errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), \"is empty\") {\n\t\t// rebuild IDs from trimmed input and retry once\n\t}\n}","preventionTips":["Trim and filter ID inputs at the CLI/API boundary","Use strings.Fields instead of strings.Split for user-supplied lists","Validate before calling; the error position tells you the index but fixing input upstream is cleaner","Deduplicate and normalize IDs in one shared helper"],"tags":["validation","edge-count","empty-id"],"backgroundTag":"invalid-request-validation","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}