gastownhall/beads · warning
ErrAssigneeMismatch
ErrAssigneeMismatch
Error message
assignee mismatch
What it means
ErrAssigneeMismatch is returned by a conditional release or update whose ExpectedAssignee does not match the issue's current assignee, including when the issue is no longer assigned at all. It means the caller's view of the claim was stale; the issue is left untouched. See Releaser.Release and UpdateRequest.ExpectedAssignee.
Source
Thrown at issueops/errors.go:62
func (e *ClaimConflictError) Unwrap() error { return e.Err }
// ErrUnsupported reports a capability this backend does not serve. It is an
// alias of beadserrors.ErrUnsupported — the same type, so one errors.As arm
// matches it under either name — and it is re-exported here because a caller
// holding an issueops role should not have to discover a second package to
// classify the refusal.
//
// It is declared there rather than here because the capability shell is not an
// issue concept: a memory role can go unimplemented by a backend exactly as a
// Reader can.
type ErrUnsupported = beadserrors.ErrUnsupported
// ErrAssigneeMismatch is returned by a conditional release or update whose
// ExpectedAssignee does not match the issue's current assignee (including when
// the issue is no longer assigned at all). The caller's view of the claim was
// stale; the issue is left untouched. See Releaser.Release and
// UpdateRequest.ExpectedAssignee.
var ErrAssigneeMismatch = errors.New("assignee mismatch")
// ErrNotOwner is returned when an actor tries to release a claim that a
// different actor holds. Releasing another actor's claim requires the force
// escape hatch (ReleaseRequest.Force, `bd unclaim --force`), reserved for
// abandoned claims — or ReleaseRequest.ExpectedAssignee, which authorizes the
// same thing by naming the holder instead of ignoring it.
//
// It is DECLARED here and re-exported by internal/storage rather than the other
// way round, which is the direction the memoryops slice settled: a Go alias
// preserves identity in both directions, so every existing storage.ErrNotOwner
// reference keeps matching this identical value, and a caller holding only the
// public role can now classify the refusal without importing an internal
// package it cannot reach. It is the ownership half of the same vocabulary
// ErrAssigneeMismatch above belongs to, which is why it is here rather than
// beside Releaser: both refusals answer "whose claim is this".
var ErrNotOwner = errors.New("issue claimed by a different actor")
// The namespace-neutral part of this vocabulary is declared by beadserrors andView on GitHub (pinned to 71377f2769)
Solutions
- Re-read the issue to get its current assignee and retry the conditional operation with the fresh value.
- If your intent was to take over, use the appropriate claim/ownership flow instead of ExpectedAssignee.
- Treat it as an optimistic-concurrency loss: errors.Is(err, issueops.ErrAssigneeMismatch) and re-run your decision logic on fresh state.
- If no one should hold it, unclaim first, then perform the conditional operation.
Example fix
// before
err := releaser.Release(ctx, types.ReleaseRequest{Issue: id, ExpectedAssignee: "alice"})
// fails if alice already unclaimed
// after
iss, _ := store.GetIssue(ctx, id)
err := releaser.Release(ctx, types.ReleaseRequest{Issue: id, ExpectedAssignee: iss.Assignee})
if errors.Is(err, issueops.ErrAssigneeMismatch) { /* re-read and retry */ } Defensive patterns
Strategy: retry
Validate before calling
iss, _ := store.GetIssue(ctx, id)
if iss.Assignee != expected {
expected = iss.Assignee // refresh before conditional op
} Try / catch
if errors.Is(err, issueops.ErrAssigneeMismatch) {
// re-read fresh assignee and retry the conditional operation
} Prevention
- Re-read the issue immediately before conditional release/update.
- Keep ExpectedAssignee sourced from a fresh read, not cached state.
- Treat mismatch as optimistic-concurrency loss and re-run decision logic.
When it happens
Trigger: Calling Releaser.Release or issuing an update with ExpectedAssignee set while the row's assignee changed (someone else claimed it, or it was unclaimed); batch apply evaluating expected assignee as modified; conditional-guard lifecycle updates with a respelled or stale assignee.
Common situations: Long-running automation holding a stale assignee snapshot; two agents coordinating via optimistic guards and one losing; an issue unclaimed between read and conditional write.
Related errors
- %w: issue %s is assigned to %q
- %w: %s is held by %q, expected %q
- ErrAlreadyClaimed
- proto %s already exists (use --force to replace)
- artifact collision for %s: %s already exists with different
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/a2460d2e71f7c7bb.
Report an issue: GitHub.