{"record":{"id":"f45dbf24c97b9b91","repo":"gastownhall/beads","slug":"w-delete-requires-at-least-one-issue-id","errorCode":null,"errorMessage":"%w: delete requires at least one issue id","messagePattern":"%w: delete requires at least one issue id","errorType":"validation","errorClass":"issueops.ErrValidation","httpStatus":null,"severity":"error","filePath":"internal/workapi/delete.go","lineNumber":35,"sourceCode":"//\n// What is NOT here is the deletion. The existence probe, the guard and the\n// erasure need one transaction (issueops.Deleter.Delete); the bodies live in\n// internal/storage/issueops/delete.go and in the unit-of-work provider.\n\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}","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/workapi/delete.go#L17-L53","documentation":"ValidateDeleteRequest wraps issueops.ErrValidation when the delete request contains no issue ids. A delete with an empty ID list is ambiguous (delete nothing? everything?), so the library refuses it up front before any storage work. This check runs in the shared validator every backend implementation uses before opening a transaction.","triggerScenarios":"Calling a delete API with issueops.DeleteRequest{IDs: nil} or IDs: []string{}; building the ID slice from a filtered list that ended up empty.","commonSituations":"Script computing IDs via a query that matched nothing; forgetting to append parsed ids; passing a request struct literal without IDs.","solutions":["Ensure at least one issue id is present before calling delete; check len(req.IDs) > 0","If the source list may legitimately be empty, skip the delete call or return a no-op in your code","Confirm your id-collection query actually returned rows"],"exampleFix":"// before\nreturn api.Delete(ctx, issueops.DeleteRequest{IDs: ids})\n// after\nif len(ids) == 0 { return nil }\nreturn api.Delete(ctx, issueops.DeleteRequest{IDs: ids})","handlingStrategy":"validation","validationCode":"if len(req.IDs) == 0 { return nil } // nothing to delete\nif err := workapi.ValidateDeleteRequest(req); err != nil { return err }","typeGuard":null,"tryCatchPattern":"if err := api.Delete(ctx, req); err != nil {\n    if errors.Is(err, issueops.ErrValidation) { return fmt.Errorf(\"delete request invalid: %w\", err) }\n    return err\n}","preventionTips":["Check len(IDs) before issuing deletes","Treat an empty id list as a legitimate no-op in callers","Collect ids only from queries you verified returned rows"],"tags":["validation","delete","issueops","empty-input"],"backgroundTag":"delete-validation-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}