{"record":{"id":"6b7295ed54313602","repo":"gastownhall/beads","slug":"close-actor-and-issue-id-must-not-be-empty","errorCode":null,"errorMessage":"close: actor and issue ID must not be empty","messagePattern":"close: actor and issue ID must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/uow/issue_operations.go","lineNumber":540,"sourceCode":"\treturn fmt.Errorf(\"%w: %w\", publicops.ErrValidation, err)\n}\n\nfunc validateUpdateRequest(request publicops.UpdateRequest) error {\n\tif request.Actor == \"\" || request.IssueID == \"\" {\n\t\treturn validationError(fmt.Errorf(\"update: actor and issue ID must not be empty\"))\n\t}\n\tif err := storageissueops.ValidateUpdateRequest(request); err != nil {\n\t\treturn validationError(err)\n\t}\n\tif err := validateMetadataPatch(request.Patch.Metadata); err != nil {\n\t\treturn validationError(err)\n\t}\n\treturn nil\n}\n\nfunc validateCloseRequest(request publicops.CloseRequest) error {\n\tif request.Actor == \"\" || request.IssueID == \"\" {\n\t\treturn validationError(fmt.Errorf(\"close: actor and issue ID must not be empty\"))\n\t}\n\treturn nil\n}\n\nfunc validateMetadataPatch(metadata publicops.MetadataPatch) error {\n\tif metadata.Replace.Set && (metadata.Merge.Set || len(metadata.Set) > 0 || len(metadata.Unset) > 0) {\n\t\treturn fmt.Errorf(\"metadata replacement cannot combine with incremental edits\")\n\t}\n\tif metadata.Replace.Set && len(metadata.Replace.Value) > 0 && !json.Valid(metadata.Replace.Value) {\n\t\treturn fmt.Errorf(\"metadata replacement is not valid JSON\")\n\t}\n\tif metadata.Merge.Set {\n\t\tvar object map[string]json.RawMessage\n\t\tif len(metadata.Merge.Value) == 0 || json.Unmarshal(metadata.Merge.Value, &object) != nil || object == nil {\n\t\t\treturn fmt.Errorf(\"metadata merge must be a JSON object\")\n\t\t}\n\t}\n\tkeys := make([]string, 0, len(metadata.Set))","sourceCodeStart":522,"sourceCodeEnd":558,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/uow/issue_operations.go#L522-L558","documentation":"validateCloseRequest requires a non-empty Actor and IssueID on every CloseRequest and wraps this rejection in publicops.ErrValidation. Closing an issue must record who closed it and which issue was closed.","triggerScenarios":"Calling Close with a publicops.CloseRequest where request.Actor == \"\" or request.IssueID == \"\".","commonSituations":"Scripted bulk-close loops where an ID list contains an empty string, a close automation with no configured actor identity, or parsing issue IDs from input that produced empty fields.","solutions":["Populate request.Actor with the closing identity","Populate request.IssueID with the target issue key","Filter/validate the ID list before looping Close calls","Check errors.Is(err, publicops.ErrValidation) to distinguish input bugs from storage failures"],"exampleFix":"// before\nfor _, id := range ids { uc.Close(ctx, publicops.CloseRequest{Actor: actor, IssueID: id}) }\n// after\nfor _, id := range ids {\n    if id == \"\" { continue }\n    uc.Close(ctx, publicops.CloseRequest{Actor: actor, IssueID: id})\n}","handlingStrategy":"validation","validationCode":"func validClose(req publicops.CloseRequest) error {\n    if req.Actor == \"\" { return fmt.Errorf(\"actor required\") }\n    if req.IssueID == \"\" { return fmt.Errorf(\"issue ID required\") }\n    return nil\n}","typeGuard":"func closeRequestComplete(req publicops.CloseRequest) bool {\n    return req.Actor != \"\" && req.IssueID != \"\"\n}","tryCatchPattern":"if err := uc.Close(ctx, req); err != nil {\n    if errors.Is(err, publicops.ErrValidation) {\n        // fix Actor/IssueID before retrying\n        return err\n    }\n    return err\n}","preventionTips":["Sanitize ID lists: drop empty strings before batch Close loops","Configure a default actor identity for automations","Validate every CloseRequest at construction time","Handle ErrValidation distinctly from transient storage errors"],"tags":["validation","close-request","go","empty-field"],"backgroundTag":"empty-required-field","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}