{"record":{"id":"617401a61d89f3b6","repo":"gastownhall/beads","slug":"errnotclaimable","errorCode":"ErrNotClaimable","errorMessage":"issue not claimable","messagePattern":"issue not claimable","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"issueops/errors.go","lineNumber":17,"sourceCode":"package issueops\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/steveyegge/beads/beadserrors\"\n)\n\n// ErrAlreadyClaimed is returned when attempting to claim an issue that is already\n// claimed by another user. The error message contains the current assignee.\nvar ErrAlreadyClaimed = errors.New(\"issue already claimed\")\n\n// ErrNotClaimable is returned when attempting to claim an issue that is not in a\n// claimable state, such as closed, deferred, or already in progress without the\n// same actor owning the claim.\nvar ErrNotClaimable = errors.New(\"issue not claimable\")\n\n// ClaimConflictError reports the state that refused a claim — the current\n// assignee and status, read inside the same transaction that lost the\n// compare-and-set. It wraps the refusal rather than replacing it, so the\n// sentinel still matches, the refusal's carefully-worded prose survives\n// byte-for-byte, and a caller can classify the conflict from typed fields\n// instead of parsing that prose.\n//\n// Err is set by every implementation that returns this type; a Claimer whose\n// same-transaction re-read fails returns the bare refusal instead.\ntype ClaimConflictError struct {\n\t// IssueID names the issue that refused the claim.\n\tIssueID string\n\t// Assignee is the holder observed by the losing transaction. It is empty\n\t// when the refusal was about the status rather than a foreign holder.\n\tAssignee string\n\t// Status is the status observed by the losing transaction.\n\tStatus Status","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/issueops/errors.go#L1-L35","documentation":"ErrNotClaimable is returned when attempting to claim an issue that is not in a claimable state, such as closed, deferred, or already in progress without the same actor owning the claim. ClaimConflictError wraps it while carrying the state (assignee and status) that refused the claim, so the sentinel still matches via errors.Is.","triggerScenarios":"Claiming an issue whose status is closed, deferred, or a configured WIP/ineligible status (e.g. built-in ineligible statuses or configured active statuses per RunClaimer tests); claiming while progress-status config rejects the transition.","commonSituations":"Race where the issue was closed between listing and claiming; claiming an issue someone already moved to in_progress; misconfigured active/WIP status lists in config making valid-looking claims refused.","solutions":["Check the issue's status first (via ClaimConflictError fields or a fresh read) and only claim open/unassigned issues.","If the issue is in progress and you are the same actor, re-assert your claim rather than a fresh claim.","Review the configured active/WIP status settings if the refusal state seems wrong for your workflow.","If the issue is closed or deferred, reopen it (if policy allows) before claiming."],"exampleFix":"// before\nerr := claimer.Claim(ctx, req) // refused: status=closed\n\n// after\niss, _ := store.GetIssue(ctx, req.ID)\nif iss.Status == \"open\" {\n    err = claimer.Claim(ctx, req)\n} else if errors.Is(err, issueops.ErrNotClaimable) {\n    var c *issueops.ClaimConflictError\n    errors.As(err, &c) // c.Status explains the refusal\n}","handlingStrategy":"validation","validationCode":"iss, _ := store.GetIssue(ctx, id)\nclaimable := iss.Status == \"open\" || (iss.Status == wipStatus && iss.Assignee == actor)\nif !claimable {\n    return fmt.Errorf(\"issue %s not claimable (status %s)\", id, iss.Status)\n}","typeGuard":null,"tryCatchPattern":"if errors.Is(err, issueops.ErrNotClaimable) {\n    var c *issueops.ClaimConflictError\n    errors.As(err, &c) // c.Status explains what refused the claim\n}","preventionTips":["Only claim issues with an open status.","Review configured active/WIP status settings after config changes.","Re-read issue state right before claiming to avoid races."],"tags":["claim","status-guard","workflow"],"backgroundTag":"issue-not-claimable","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}