gastownhall/beads · error
failed to get issue for unclaim: %w
Error message
failed to get issue for unclaim: %w
What it means
UnclaimIssueInTx wraps the underlying GetIssueInTx error with this message when it cannot re-read the issue inside the transaction before unclaiming. The library throws it because the claim must be validated (status, assignee, ownership) against the current row; a read failure aborts the unclaim safely without modifying data.
Source
Thrown at internal/storage/issueops/unclaim.go:43
// (admin/reaper use, threaded from `bd unclaim --force`).
//
// Only works on issues that have an assignee and status is "open" or
// "in_progress". Returns error if:
// - Issue is closed (cannot unclaim closed issues)
// - Issue has no assignee (nothing to unclaim)
// - Issue is claimed by a different actor and force is false (ErrNotOwner)
//
//nolint:gosec // G201: table names come from WispTableRouting (hardcoded constants)
func UnclaimIssueInTx(ctx context.Context, tx DBTX, id string, actor string, force bool) error {
// Route to the correct table (issues/wisps) automatically, matching
// ClaimIssueInTx — a wisp claim lives in the wisp tables, so its release
// must update them too rather than no-op against the permanent issues table.
isWisp := IsActiveWispInTx(ctx, tx, id)
issueTable, _, eventTable, _ := WispTableRouting(isWisp)
oldIssue, err := GetIssueInTx(ctx, tx, id)
if err != nil {
return fmt.Errorf("failed to get issue for unclaim: %w", err)
}
// Validate: cannot unclaim closed issues
if oldIssue.Status == types.StatusClosed {
return fmt.Errorf("cannot unclaim closed issue %s", id)
}
// Validate: must have an assignee to unclaim
if oldIssue.Assignee == "" {
return fmt.Errorf("issue %s is not assigned", id)
}
// Validate ownership unless the caller forced the release. Without force, a
// process may only release its own claim. Compared under actorMatches, not
// verbatim, so a caller naming its own identity under a different layer's
// spelling (ga-5ksp5) is not refused as a stranger.
if !force && !actorMatches(oldIssue.Assignee, actor) {
return fmt.Errorf("%w: %s is held by %s; coordinate with the holder — pass --force only if their claim is abandoned (crashed agent, expired lease)",View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped cause with errors.Unwrap/errors.Is to distinguish not-found from connection failure.
- Verify the issue ID exists (e.g. bd show <id> or a Get call) before releasing.
- Retry once on a transient DB/connection error with a fresh transaction.
- Check database connectivity and file permissions if failures persist.
Example fix
// before
err := store.ReleaseIssue(ctx, "bd-abc") // stale/garbage id
// after
iss, err := store.GetIssue(ctx, "bd-abc")
if err != nil { return err } // resolve correct id first
err = store.ReleaseIssue(ctx, iss.ID) Defensive patterns
Strategy: try-catch
Validate before calling
if _, err := store.GetIssue(ctx, id); err != nil {
return fmt.Errorf("cannot release %s: %w", id, err)
} Try / catch
if err := store.ReleaseIssue(ctx, id); err != nil {
if isNotFound(err) { /* recreate/skip */ } else if isTransientDBErr(err) { /* retry with backoff */ }
} Prevention
- Confirm issue IDs via Get/list before mutating operations.
- Keep the database (Dolt) reachable and healthy; monitor connection errors.
- Don't cache issue IDs across long-running processes without revalidation.
When it happens
Trigger: Calling ReleaseIssueInTx/UnclaimIssueInTx with an issue ID that fails the internal SELECT: nonexistent ID, DB connection failure, transaction already aborted, or driver-level read error.
Common situations: Typo'd or stale issue ID from a cached handle; database unreachable or locked; concurrent transaction rolled back the row mid-flight.
Related errors
- failed to unclaim issue: %w
- ErrTransaction
- open unit of work: %w
- failed to begin transaction: %w
- failed to commit is_blocked repairs: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f3572b7e61027563.
Report an issue: GitHub.