{"record":{"id":"5498ead2601ae462","repo":"gastownhall/beads","slug":"cannot-reassign-s-held-by-q-in-progress-coor","errorCode":null,"errorMessage":"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","messagePattern":"cannot reassign (.+?): held by %q \\(in_progress\\); coordinate with the holder \\(bd mail (.+?)\\) — pass --force only if their claim is abandoned \\(crashed agent, expired lease\\), or use bd reclaim","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/validation/issue.go","lineNumber":220,"sourceCode":"// cross-actor takeovers without adding new identity guarantees, and without\n// falsely treating the current holder as a stranger when actor or\n// newAssignee names it under a different layer's spelling of the same\n// identity.\nfunc AssigneeNotStolen(actor, newAssignee string, poolAliases func() []string, force bool) IssueValidator {\n\treturn func(id string, issue *types.Issue) error {\n\t\tif issue == nil || force {\n\t\t\treturn nil\n\t\t}\n\t\tif issue.Assignee == \"\" || ActorMatches(issue.Assignee, actor) || ActorMatches(issue.Assignee, newAssignee) {\n\t\t\treturn nil\n\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 {","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/validation/issue.go#L202-L238","documentation":"The reassignment validator fences issues currently in 'in_progress' status. The issue is actively held by an assignee (outside the shared pool-alias set), so reassignment is rejected to avoid stealing a live claim.","triggerScenarios":"An assignee update on an issue with Status=in_progress, where the current assignee is not in the poolAliases allowlist (i.e. a real individual claim, not a shared pool identity).","commonSituations":"Orchestrators re-dispatching an issue already claimed by a running agent; a human reassigning work without noticing the holder is mid-task; crashed agents leaving stale in_progress claims (the intended --force case).","solutions":["Message the holder first: 'bd mail <assignee>' to coordinate","Use 'bd reclaim' to properly take over the claim","Pass --force only if the holder's claim is abandoned (crashed agent, expired lease)","If the issue is genuinely free, have the holder set it back from in_progress before reassigning"],"exampleFix":"// before\nbd update bd-42 assignee=agent-2  // held by agent-1 in_progress → blocked\n// after\nbd reclaim bd-42   # or: bd mail agent-1 to coordinate\nbd update bd-42 assignee=agent-2","handlingStrategy":"validation","validationCode":"if issue.Status == types.StatusInProgress && !slices.Contains(poolAliases(), issue.Assignee) {\n    return fmt.Errorf(\"%s is in_progress and held by %s; reclaim or coordinate first\", issue.ID, issue.Assignee)\n}","typeGuard":"func isLiveClaim(issue *types.Issue, poolAliases func() []string) bool {\n    return issue != nil && issue.Status == types.StatusInProgress &&\n        (poolAliases == nil || !slices.Contains(poolAliases(), issue.Assignee))\n}","tryCatchPattern":"if err := reassign(id, newAssignee); err != nil {\n    if strings.Contains(err.Error(), \"held by\") {\n        // verify holder is gone (crashed/expired) before forcing\n        if err := reclaim(id); err != nil { return err }\n        return reassign(id, newAssignee)\n    }\n    return err\n}","preventionTips":["Check issue.Status before reassigning; only reassign open issues freely","Message the holder with 'bd mail <assignee>' before taking over in_progress work","Use --force only for provably abandoned claims (crashed agent, expired lease)","Prefer 'bd reclaim' as the sanctioned takeover path"],"tags":["validation","reassign","in-progress","concurrency","claim"],"backgroundTag":"claim-conflict","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}