{"record":{"id":"d420857feb741464","repo":"gastownhall/beads","slug":"reopen-actor-and-issue-id-must-not-be-empty","errorCode":null,"errorMessage":"reopen: actor and issue ID must not be empty","messagePattern":"reopen: actor and issue ID must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/uow/issue_operations.go","lineNumber":581,"sourceCode":"\tfor _, key := range keys {\n\t\tif err := storage.ValidateMetadataKey(key); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !json.Valid(metadata.Set[key]) {\n\t\t\treturn fmt.Errorf(\"metadata value for key %q is not valid JSON\", key)\n\t\t}\n\t}\n\tfor _, key := range metadata.Unset {\n\t\tif err := storage.ValidateMetadataKey(key); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc validateReopenRequest(request publicops.ReopenRequest) error {\n\tif request.Actor == \"\" || request.IssueID == \"\" {\n\t\treturn validationError(fmt.Errorf(\"reopen: actor and issue ID must not be empty\"))\n\t}\n\treturn nil\n}\n\nfunc semanticIssueEqual(left, right *types.Issue) bool {\n\tif left == nil || right == nil {\n\t\treturn left == right\n\t}\n\tleftCopy := *left\n\trightCopy := *right\n\tleftCopy.UpdatedAt, rightCopy.UpdatedAt = time.Time{}, time.Time{}\n\tleftCopy.RowVersion, rightCopy.RowVersion = 0, 0\n\treturn reflect.DeepEqual(leftCopy, rightCopy)\n}\n","sourceCodeStart":563,"sourceCodeEnd":596,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/uow/issue_operations.go#L563-L596","documentation":"validateReopenRequest requires a non-empty Actor and IssueID on every ReopenRequest, wrapping this rejection in publicops.ErrValidation. Reopening records who performed the action and on which issue.","triggerScenarios":"Calling Reopen with a publicops.ReopenRequest where request.Actor == \"\" or request.IssueID == \"\".","commonSituations":"A reopen automation with no actor identity configured, IDs parsed from input that yielded empty strings, or a request struct partially populated after copy-paste from another operation.","solutions":["Set request.Actor to the acting identity before calling Reopen","Set request.IssueID to the target issue key","Guard the call site: skip or fix requests with empty fields","Trace where the empty value originates (config, env, parsing) and add a default or error"],"exampleFix":"// before\nreq := publicops.ReopenRequest{IssueID: id}\n// after\nreq := publicops.ReopenRequest{Actor: actor, IssueID: id}\nif req.Actor == \"\" || req.IssueID == \"\" { return fmt.Errorf(\"reopen aborted: missing actor/id\") }","handlingStrategy":"validation","validationCode":"func validReopen(req publicops.ReopenRequest) 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 reopenRequestComplete(req publicops.ReopenRequest) bool {\n    return req.Actor != \"\" && req.IssueID != \"\"\n}","tryCatchPattern":"if err := uc.Reopen(ctx, req); err != nil {\n    if errors.Is(err, publicops.ErrValidation) {\n        // input bug: populate Actor/IssueID, do not retry blindly\n        return err\n    }\n    return err\n}","preventionTips":["Require actor as a parameter in reopen helper functions","Validate IDs parsed from user input before building ReopenRequest","Set a default actor for automated reopen flows","Keep request construction centralized and unit-test field completeness"],"tags":["validation","reopen-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"}