{"record":{"id":"918eaba052d99ed8","repo":"gastownhall/beads","slug":"w-delete-id-at-position-d-is-blank","errorCode":null,"errorMessage":"%w: delete id at position %d is blank","messagePattern":"%w: delete id at position (.+?) is blank","errorType":"validation","errorClass":"issueops.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/workapi/delete.go","lineNumber":39,"sourceCode":"\n// ValidateDeleteRequest applies the request rules every Deleter implementation\n// shares, before anything is read.\n//\n// There is deliberately no require-a-filter analog of the sweep gate here: a\n// delete request carries no predicate at all, so a caller cannot spell\n// \"everything\" without typing every id. The guard that does matter — dependents\n// outside the request — needs the graph and therefore lives in the bodies.\n//\n// The ExpectedVersion arity rule is here rather than in the bodies for the\n// reason the rest of this file exists: it needs no database, and a rule the\n// bodies each spelled themselves is a rule that can differ per backend.\nfunc ValidateDeleteRequest(in issueops.DeleteRequest) error {\n\tif len(in.IDs) == 0 {\n\t\treturn fmt.Errorf(\"%w: delete requires at least one issue id\", issueops.ErrValidation)\n\t}\n\tfor i, id := range in.IDs {\n\t\tif strings.TrimSpace(id) == \"\" {\n\t\t\treturn fmt.Errorf(\"%w: delete id at position %d is blank\", issueops.ErrValidation, i)\n\t\t}\n\t}\n\t// DISTINCT ids, not mentions: DeleteRequest.IDs promises duplicates\n\t// collapse, so an IDs of {\"a\", \"a\"} carrying a version names ONE row and is\n\t// legal. Counting the raw slice here would refuse the request the role's own\n\t// normalization rule says is fine.\n\tif in.ExpectedVersion != nil {\n\t\tif distinct := len(NormalizeDeleteIDs(in.IDs)); distinct > 1 {\n\t\t\treturn fmt.Errorf(\"%w: expected-version delete names %d issues; one row version cannot describe more than one row\",\n\t\t\t\tissueops.ErrValidation, distinct)\n\t\t}\n\t}\n\treturn nil\n}\n\n// NormalizeDeleteIDs collapses duplicates, keeping the caller's FIRST mention\n// of each id, and trims surrounding whitespace.\n//","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/workapi/delete.go#L21-L57","documentation":"ValidateDeleteRequest wraps issueops.ErrValidation when an id at a specific position in the IDs slice is blank (whitespace-only after trimming). Blank entries would silently target nothing, so the validator reports the exact zero-based position that is bad.","triggerScenarios":"Calling delete with IDs like [\"bd-1\", \"  \", \"bd-3\"]; splitting a comma-separated string that contains empty segments (e.g. \"bd-1,,bd-3\").","commonSituations":"strings.Split on user input producing empty fields; id fields sourced from sparse config or malformed JSON arrays; copy-paste artifacts.","solutions":["Filter blank entries before calling delete: keep only ids where strings.TrimSpace(id) != \"\"","Fix the upstream parsing so empty segments are dropped or rejected","Use workapi.NormalizeDeleteIDs to collapse and clean the id list first"],"exampleFix":"// before\nids := strings.Split(input, \",\")\nreq := issueops.DeleteRequest{IDs: ids}\n// after\nvar ids []string\nfor _, s := range strings.Split(input, \",\") {\n    if v := strings.TrimSpace(s); v != \"\" { ids = append(ids, v) }\n}\nreq := issueops.DeleteRequest{IDs: ids}","handlingStrategy":"validation","validationCode":"cleaned := workapi.NormalizeDeleteIDs(req.IDs)\nfor i, id := range cleaned {\n    if strings.TrimSpace(id) == \"\" { return fmt.Errorf(\"blank id at %d\", i) }\n}\nreq.IDs = cleaned","typeGuard":null,"tryCatchPattern":"if err := api.Delete(ctx, req); err != nil {\n    if errors.Is(err, issueops.ErrValidation) && strings.Contains(err.Error(), \"is blank\") {\n        req.IDs = workapi.NormalizeDeleteIDs(req.IDs)\n        return api.Delete(ctx, req)\n    }\n    return err\n}","preventionTips":["Split delimited id input with strings.Fields or filter empty segments","Run NormalizeDeleteIDs on all externally sourced id lists","Validate ids at parse time, not at API time"],"tags":["validation","delete","blank-id","issueops"],"backgroundTag":"delete-validation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}