{"record":{"id":"6a2b28210ce8a5f7","repo":"gastownhall/beads","slug":"w-close-batch-requires-an-actor","errorCode":null,"errorMessage":"%w: close batch requires an actor","messagePattern":"%w: close batch requires an actor","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/storage/issueops/close_batch.go","lineNumber":20,"sourceCode":"\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\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// ValidateCloseBatchRequest applies the request rules every BatchCloser\n// implementation shares, so a rule is a contract rather than one backend's\n// habit. It rejects the request outright; a per-item refusal is a result, not\n// a validation failure, and never reaches here.\nfunc ValidateCloseBatchRequest(request publicops.CloseBatchRequest) error {\n\tif request.Actor == \"\" {\n\t\treturn fmt.Errorf(\"%w: close batch requires an actor\", storage.ErrValidation)\n\t}\n\tif len(request.Items) == 0 {\n\t\treturn fmt.Errorf(\"%w: close batch requires at least one item\", storage.ErrValidation)\n\t}\n\tfor i, item := range request.Items {\n\t\tif item.IssueID == \"\" {\n\t\t\treturn fmt.Errorf(\"%w: close batch item %d requires an issue ID\", storage.ErrValidation, i)\n\t\t}\n\t}\n\tif request.ClaimNext != nil {\n\t\tif err := ValidateClaimNextRequest(publicops.ClaimNextRequest{Actor: request.Actor, Filter: *request.ClaimNext}); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// CloseBatchCommitMessage is the history entry a batch records. It is the","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/close_batch.go#L2-L38","documentation":"ValidateCloseBatchRequest rejects a CloseBatchRequest whose Actor field is empty, wrapping storage.ErrValidation so callers can match with errors.Is. Batch close operations must attribute every close to an actor for the audit event, so an unattributed request is a programming/API misuse rejected before any storage work. Per-item refusals are results, not this validation error.","triggerScenarios":"Constructing publicops.CloseBatchRequest without setting Actor (or setting it to \"\") and passing it to a BatchCloser implementation that runs shared validation first.","commonSituations":"Copy-pasted request structs missing the Actor field; refactored callers that dropped actor propagation; tests building requests with only Items populated; deserialized JSON lacking the actor key.","solutions":["Set request.Actor to the operator/session identity before calling the batch close API.","Match with errors.Is(err, storage.ErrValidation) and surface a clear 'actor required' message to the user.","In server/CLI layers, require an --actor flag or authenticated identity and fail fast before building the request.","Add a constructor/helper for CloseBatchRequest that makes Actor a required argument."],"exampleFix":"// before\nreq := publicops.CloseBatchRequest{Items: items}\nerr := closer.CloseBatch(ctx, req)\n// after\nreq := publicops.CloseBatchRequest{Actor: actor, Items: items}\nif req.Actor == \"\" {\n    return fmt.Errorf(\"close batch: actor is required\")\n}\nerr := closer.CloseBatch(ctx, req)","handlingStrategy":"validation","validationCode":"func validateCloseReq(req publicops.CloseBatchRequest) error {\n    if req.Actor == \"\" {\n        return fmt.Errorf(\"close batch requires an actor\")\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := ValidateCloseBatchRequest(req); err != nil {\n    if errors.Is(err, storage.ErrValidation) {\n        return fmt.Errorf(\"invalid close batch request: %w\", err)\n    }\n    return err\n}","preventionTips":["Make Actor a required constructor argument for CloseBatchRequest.","Thread the authenticated identity through every layer instead of defaulting to \"\".","Validate the request at the CLI/HTTP boundary before reaching storage.","Add tests asserting requests without Actor are rejected."],"tags":["validation","batch-close","api-misuse","audit"],"backgroundTag":"missing-actor-validation","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}