{"record":{"id":"f6fa4dbfaa491f56","repo":"gastownhall/beads","slug":"unclaim-w","errorCode":null,"errorMessage":"Unclaim: %w","messagePattern":"Unclaim: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/issue.go","lineNumber":1883,"sourceCode":"\t\treturn nil, fmt.Errorf(\"GetStaleIssues: %w\", err)\n\t}\n\treturn out, nil\n}\n\nfunc (u *issueUseCaseImpl) GetEpicsEligibleForClosure(ctx context.Context) ([]*types.EpicStatus, error) {\n\tout, err := u.issueRepo.GetEpicsEligibleForClosure(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"GetEpicsEligibleForClosure: %w\", err)\n\t}\n\treturn out, nil\n}\n\nfunc (u *issueUseCaseImpl) Unclaim(ctx context.Context, id, actor string, force bool) error {\n\tif id == \"\" {\n\t\treturn fmt.Errorf(\"Unclaim: id must not be empty\")\n\t}\n\tif err := u.issueRepo.UnclaimIssue(ctx, id, actor, force); err != nil {\n\t\treturn fmt.Errorf(\"Unclaim: %w\", err)\n\t}\n\treturn nil\n}\n\n// UnclaimIfAssignee is the compare-and-swap release: it clears the claim only\n// while the issue is still assigned to expectedAssignee, and otherwise returns\n// storage.ErrAssigneeMismatch having written nothing. It is the conditional\n// twin of Unclaim and runs the SAME transition (assignee cleared, status\n// reopened, started_at cleared, lease dropped, row_lock rewritten, \"unclaimed\"\n// event recorded) because both reach the one classic implementation in\n// issueops — which is what makes `bd unclaim --if-assignee` behave identically\n// on the proxied-server and embedded backends.\nfunc (u *issueUseCaseImpl) UnclaimIfAssignee(ctx context.Context, id, actor, expectedAssignee string) error {\n\tif id == \"\" {\n\t\treturn fmt.Errorf(\"UnclaimIfAssignee: id must not be empty\")\n\t}\n\tif err := u.issueRepo.UnclaimIssueIfAssignee(ctx, id, actor, expectedAssignee); err != nil {\n\t\treturn fmt.Errorf(\"UnclaimIfAssignee: %w\", err)","sourceCodeStart":1865,"sourceCodeEnd":1901,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/issue.go#L1865-L1901","documentation":"Wrapped error from Unclaim: the underlying issueRepo.UnclaimIssue call failed and is returned with an \"Unclaim: \" prefix. At this point the empty-id validation has passed; the failure originates in the storage layer while releasing an issue claim.","triggerScenarios":"Calling Unclaim(ctx, id, actor, force) where issueRepo.UnclaimIssue fails: the issue does not exist, the actor does not hold the claim, concurrent modification, or a database error.","commonSituations":"Two agents racing to unclaim/update the same issue; unclaiming an issue that was already reassigned or deleted; transient DB outages during CI automation.","solutions":["Unwrap the error to identify the repository-level cause (not-found vs. conflict vs. connectivity)","Verify the issue exists and inspect its current assignee before unclaiming","Use UnclaimIfAssignee for compare-and-swap semantics if races are the problem","Retry on transient storage errors"],"exampleFix":"// before\nif err := uc.Unclaim(ctx, id, actor, true); err != nil { return err }\n// after\nif err := uc.Unclaim(ctx, id, actor, true); err != nil {\n\tvar nf types.ErrNotFound\n\tif errors.As(err, &nf) { return fmt.Errorf(\"issue %s gone\", id) }\n\treturn fmt.Errorf(\"unclaim %s: %w\", id, err)\n}","handlingStrategy":"try-catch","validationCode":"issue, err := uc.Show(ctx, id)\nif err != nil || issue == nil {\n\treturn fmt.Errorf(\"issue %s not found, skipping unclaim\", id)\n}","typeGuard":null,"tryCatchPattern":"if err := uc.Unclaim(ctx, id, actor, force); err != nil {\n\tvar nf types.ErrNotFound\n\tif errors.As(err, &nf) { return nil /* already gone */ }\n\treturn fmt.Errorf(\"unclaim %s: %w\", id, err)\n}","preventionTips":["Check the issue's current assignee before unclaiming","Prefer UnclaimIfAssignee when multiple actors may race","Treat not-found as success in idempotent cleanup loops"],"tags":["storage","error-wrapping","claim"],"backgroundTag":"repo-error-wrapping","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}