{"record":{"id":"a7fa98822631e268","repo":"gastownhall/beads","slug":"w-read-edges-id-d-is-empty","errorCode":null,"errorMessage":"%w: read edges id %d is empty","messagePattern":"%w: read edges id (.+?) is empty","errorType":"validation","errorClass":"storage.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/edges.go","lineNumber":22,"sourceCode":"\t\"context\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/steveyegge/beads/internal/storage\"\n\t\"github.com/steveyegge/beads/internal/types\"\n\tpublicops \"github.com/steveyegge/beads/issueops\"\n)\n\n// ValidateEdgeReadRequest applies the request rules every EdgeReader\n// implementation shares. Both tell a caller's mistake from a legitimately empty\n// answer: an empty ID entry names nothing, and an unusable dependency type would\n// become a filter that silently matches nothing. An empty ID SLICE is neither —\n// it asks about no anchors and gets none back.\nfunc ValidateEdgeReadRequest(request publicops.EdgeReadRequest) error {\n\tfor i, id := range request.IDs {\n\t\tif id == \"\" {\n\t\t\treturn fmt.Errorf(\"%w: read 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: read 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// EdgeReadAnchors is the de-duplicated anchor list a read runs against: the\n// request's ids with repeats collapsed onto their first mention.\n//\n// It is shared rather than a loop in each implementation because the\n// de-duplication decides the SHAPE of the answer — one entry per distinct id, in\n// first-mention order. BlockingAnnotator makes the same promise over the same\n// shape of request (blocking_annotation.go) and reaches it here too.","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/edges.go#L4-L40","documentation":"ValidateEdgeReadRequest rejects an EdgeReadRequest whose IDs slice contains an empty string at position %d. An empty ID names no issue, so the library refuses rather than returning a misleading per-anchor entry. It wraps storage.ErrValidation; an entirely empty IDs slice is valid and returns an empty result.","triggerScenarios":"Calling ReadEdges / ExecuteEdgeRead with EdgeReadRequest{IDs: []string{\"bd-1\", \"\"}}; building IDs from strings.Split on a comma list with blank or trailing elements; deserializing a JSON array containing \"\".","commonSituations":"Shell scripts passing untrimmed arguments; a UI sending a blank row from an editable list; generators that append empty IDs when an optional reference is unset.","solutions":["Filter empty strings out of request.IDs before the call (slices.DeleteFunc or a loop).","Fix the input producer: use strings.Fields or skip empty segments when splitting.","Trim whitespace first so ids like \" \" are caught and dropped rather than passing an invalid-but-nonempty value.","Validate IDs at the CLI/API boundary and reject blank arguments with a clear message."],"exampleFix":"// before\nreq := publicops.EdgeReadRequest{IDs: strings.Split(list, \",\")}\n// after\nvar ids []string\nfor _, s := range strings.Split(list, \",\") {\n\tif s = strings.TrimSpace(s); s != \"\" { ids = append(ids, s) }\n}\nreq := publicops.EdgeReadRequest{IDs: ids}","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 := ValidateEdgeReadRequest(req); err != nil {\n\tif errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), \"is empty\") {\n\t\treq.IDs = nonEmptyIDs(req.IDs)\n\t}\n}","preventionTips":["Filter/trim IDs before building EdgeReadRequest","Use strings.Fields for whitespace-delimited input","Validate at the boundary where user input enters the program","Reuse a shared ID-normalization helper across read and count paths"],"tags":["validation","edge-read","empty-id"],"backgroundTag":"invalid-request-validation","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}