{"record":{"id":"e6325f4f491e5341","repo":"gastownhall/beads","slug":"issue-s-is-already-closed-e6325f","errorCode":null,"errorMessage":"issue %s is already closed","messagePattern":"issue (.+?) is already closed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/validation/issue.go","lineNumber":231,"sourceCode":"\t\t}\n\t\tif issue.Status != types.StatusInProgress {\n\t\t\treturn nil\n\t\t}\n\t\tif poolAliases != nil && slices.Contains(poolAliases(), issue.Assignee) {\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"cannot reassign %s: held by %q (in_progress); coordinate with the holder (bd mail %s) — pass --force only if their claim is abandoned (crashed agent, expired lease), or use bd reclaim\", id, issue.Assignee, issue.Assignee)\n\t}\n}\n\n// NotClosed validates that an issue is not already closed.\nfunc NotClosed() IssueValidator {\n\treturn func(id string, issue *types.Issue) error {\n\t\tif issue == nil {\n\t\t\treturn nil\n\t\t}\n\t\tif issue.Status == types.StatusClosed {\n\t\t\treturn fmt.Errorf(\"issue %s is already closed\", id)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// NotHooked validates that an issue is not in hooked status.\nfunc NotHooked(force bool) IssueValidator {\n\treturn func(id string, issue *types.Issue) error {\n\t\tif issue == nil {\n\t\t\treturn nil\n\t\t}\n\t\tif !force && issue.Status == types.StatusHooked {\n\t\t\treturn fmt.Errorf(\"cannot modify hooked issue %s (use --force to override)\", id)\n\t\t}\n\t\treturn nil\n\t}\n}\n","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/validation/issue.go#L213-L249","documentation":"This error is returned by the NotClosed issue validator when an operation is attempted on an issue whose Status is 'closed'. The library treats closed issues as immutable endpoints of their lifecycle, so mutations (close, update, etc.) against them are rejected to preserve history integrity. The message includes the issue ID so the caller knows which issue is blocked.","triggerScenarios":"Calling any operation that runs the NotClosed validator (e.g. bd close or status-changing updates) with the ID of an issue whose issue.Status == types.StatusClosed; passing a nil issue returns nil, so only a non-nil closed issue triggers it.","commonSituations":"Scripts that close issues in a loop without checking current status (double-close), retrying a close command after a partially-failed batch, concurrent agents/processes closing the same issue, or stale local IDs fetched before another process closed the issue.","solutions":["Check the issue's current status before mutating (bd show <id> or the API) and skip if already closed","If the operation is idempotent by design, filter out closed issues client-side before invoking the validator-backed call","If you genuinely must modify a closed issue, reopen it first (set status back to open) then perform the mutation","Treat the error as success in idempotent close workflows by matching on the 'is already closed' message"],"exampleFix":"// before\nfor _, id := range ids {\n    bd.Close(id) // fails on already-closed issues\n}\n// after\nfor _, id := range ids {\n    issue := getIssue(id)\n    if issue != nil && issue.Status != types.StatusClosed {\n        bd.Close(id)\n    }\n}","handlingStrategy":"validation","validationCode":"func canClose(issue *types.Issue) bool {\n    return issue != nil && issue.Status != types.StatusClosed\n}","typeGuard":"func isOpen(issue *types.Issue) bool {\n    return issue != nil && issue.Status != types.StatusClosed\n}","tryCatchPattern":"if err := closeIssue(id); err != nil {\n    if strings.Contains(err.Error(), \"is already closed\") {\n        return nil // idempotent close\n    }\n    return err\n}","preventionTips":["Always fetch and inspect issue status before lifecycle mutations","Make close operations idempotent by skipping already-closed issues","In batch jobs, filter out closed issues before processing","Refresh issue data from the source before acting on cached IDs"],"tags":["validation","issue-lifecycle","state-conflict"],"backgroundTag":"issue-already-closed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}