gastownhall/beads · error

ErrNotOwner

ErrNotOwner

Error message

issue claimed by a different actor

What it means

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`) for abandoned claims, or ReleaseRequest.ExpectedAssignee naming the holder. It preserves the identity of storage.ErrNotOwner so existing errors.Is sites keep matching, letting public-role callers classify the refusal without importing internal packages.

Source

Thrown at issueops/errors.go:78

// 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 and
// re-exported here. These are ALIASES, so they are the same values: every
// existing issueops.ErrX reference and every errors.Is site keeps matching the
// identical error, and a leaf that never imports issueops still matches it too.
//
// They live down there because none of them names an issue: a request can be
// invalid, a row can be missing and a database can be uninitialized on any
// plane. The refusals BELOW that name issue concepts stay here.
var (
	// ErrNotFound is returned when a requested entity does not exist in the database.
	ErrNotFound = beadserrors.ErrNotFound
	// ErrValidation classifies deterministic request-validation failures.
	ErrValidation = beadserrors.ErrValidation
	// ErrNotInitialized is returned when the database has not been initialized
	// (e.g., issue_prefix config is missing).
	ErrNotInitialized = beadserrors.ErrNotInitialized
)

View on GitHub (pinned to 71377f2769)

Solutions

  1. If the claim is abandoned, pass ReleaseRequest.Force = true (or `bd unclaim --force`).
  2. Alternatively name the holder explicitly with ReleaseRequest.ExpectedAssignee to authorize releasing a foreign claim.
  3. Verify the actor identity used by your tooling matches the claimant (env/config).
  4. Have the actual holder release their own claim.

Example fix

// before
err := releaser.Release(ctx, types.ReleaseRequest{Issue: id}) // actor != holder

// after
err := releaser.Release(ctx, types.ReleaseRequest{Issue: id, Force: true})
// or: ExpectedAssignee: "alice" to name the holder
Defensive patterns

Strategy: type-guard

Validate before calling

iss, _ := store.GetIssue(ctx, id)
if iss.Assignee != "" && iss.Assignee != actor {
    // foreign claim: need Force or ExpectedAssignee
}

Try / catch

if errors.Is(err, issueops.ErrNotOwner) {
    // retry with ReleaseRequest.Force or ExpectedAssignee naming the holder
}

Prevention

When it happens

Trigger: Calling Releaser.Release (or UnclaimIssueInTx) as actor X on an issue claimed by actor Y without Force or ExpectedAssignee; scripted cleanup running under a service account trying to unclaim user-held issues.

Common situations: Cleaning up a departed teammate's abandoned claims; CI bots attempting to unclaim human-held issues; mismatched actor identity (different username/env) from the one that claimed.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/a721430092139d7e. Report an issue: GitHub.