gastownhall/beads · error
cannot close %s: assignee is %q, actor is %q; reclaim or use
Error message
cannot close %s: assignee is %q, actor is %q; reclaim or use --force to override
What it means
The close-assignee validator prevents an actor from closing an issue assigned to someone else. If the issue has a non-empty assignee that does not match the acting identity, the close is rejected until the actor reclaims or forces.
Source
Thrown at internal/validation/issue.go:173
// CanonicalActor, so two principals sharing one canonical actor name both
// pass — including the same principal spelled two different ways by two
// different layers of Gas Town (ga-wzl83). bd has no identity layer, so this
// matches the existing semantics of the actor field — the guard removes
// silent cross-actor closes without adding new identity guarantees.
//
// This guards against the silent-success bug where actor A closes a bead that
// was concurrently re-claimed by actor B: storage accepts the close (id-only
// WHERE clause), so without this check bd reports "✓ Closed" even though A had
// no authority over the bead. (be-035)
func AssigneeMatches(actor string, force bool) IssueValidator {
return func(id string, issue *types.Issue) error {
if issue == nil || force {
return nil
}
if issue.Assignee == "" || ActorMatches(issue.Assignee, actor) {
return nil
}
return fmt.Errorf("cannot close %s: assignee is %q, actor is %q; reclaim or use --force to override", id, issue.Assignee, actor)
}
}
// AssigneeNotStolen validates that a plain assignee update does not silently
// overwrite another actor's live claim (bd-98s5c). newAssignee is the value
// the write would set (may be "" for an unassign — fenced the same way, since
// stripping a live claim is what bd unclaim refuses without --force).
//
// The refusal fires only when every clause holds; each one is load-bearing:
// - Assignee != "" — unassigned issues are freely
// assignable.
// - Assignee doesn't canonicalize to actor — an actor editing its
// own claim is untouched, under any spelling of its own identity.
// - Assignee doesn't canonicalize to newAssignee — an idempotent re-assert
// of the current holder stays a success (retry/replay safety on the
// proxied path), even when the re-assert names the holder under a
// different spelling.
// - Status == in_progress — reassigning an OPEN bead stays frictionless:View on GitHub (pinned to 71377f2769)
Solutions
- Run 'bd reclaim <id>' to take over the claim, then close
- Pass --force if you are certain the assignee's work is done or abandoned
- Close as the correct actor (match the assignee identity, e.g. same agent name)
- Coordinate with the assignee (bd mail) before closing
Example fix
// before bd close bd-42 // actor=alice, assignee=agent-1 → blocked // after bd reclaim bd-42 bd close bd-42
Defensive patterns
Strategy: validation
Validate before calling
if issue.Assignee != "" && issue.Assignee != currentActor {
return fmt.Errorf("%s is claimed by %s; run 'bd reclaim' first", issue.ID, issue.Assignee)
} Type guard
func actorOwns(issue *types.Issue, actor string) bool { return issue != nil && (issue.Assignee == "" || issue.Assignee == actor) } Try / catch
if err := closeIssue(id); err != nil {
if strings.Contains(err.Error(), "assignee is") {
if err := reclaim(id); err != nil { return err }
return closeIssue(id)
}
return err
} Prevention
- Always reclaim before operating on issues claimed by others
- Keep your actor identity consistent across sessions and scripts
- Coordinate via 'bd mail' when multiple agents share an issue
When it happens
Trigger: Running 'bd close <id>' (without --force) where issue.Assignee is set, non-empty, and ActorMatches(assignee, actor) is false.
Common situations: Two agents on the same issue: one finishes and closes while the other still holds the claim; a human closing an agent's in-progress issue; identity configured differently (different actor string) than the one that claimed the issue.
Related errors
- ErrNotClaimable
- cannot reassign %s: held by %q (in_progress); coordinate wit
- ErrAlreadyClaimed
- Unclaim: id must not be empty
- UnclaimIfAssignee: id must not be empty
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/aa177c49bd51bec3.
Report an issue: GitHub.