{"record":{"id":"f3572b7e61027563","repo":"gastownhall/beads","slug":"failed-to-get-issue-for-unclaim-w","errorCode":null,"errorMessage":"failed to get issue for unclaim: %w","messagePattern":"failed to get issue for unclaim: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/unclaim.go","lineNumber":43,"sourceCode":"// (admin/reaper use, threaded from `bd unclaim --force`).\n//\n// Only works on issues that have an assignee and status is \"open\" or\n// \"in_progress\". Returns error if:\n//   - Issue is closed (cannot unclaim closed issues)\n//   - Issue has no assignee (nothing to unclaim)\n//   - Issue is claimed by a different actor and force is false (ErrNotOwner)\n//\n//nolint:gosec // G201: table names come from WispTableRouting (hardcoded constants)\nfunc UnclaimIssueInTx(ctx context.Context, tx DBTX, id string, actor string, force bool) error {\n\t// Route to the correct table (issues/wisps) automatically, matching\n\t// ClaimIssueInTx — a wisp claim lives in the wisp tables, so its release\n\t// must update them too rather than no-op against the permanent issues table.\n\tisWisp := IsActiveWispInTx(ctx, tx, id)\n\tissueTable, _, eventTable, _ := WispTableRouting(isWisp)\n\n\toldIssue, err := GetIssueInTx(ctx, tx, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to get issue for unclaim: %w\", err)\n\t}\n\n\t// Validate: cannot unclaim closed issues\n\tif oldIssue.Status == types.StatusClosed {\n\t\treturn fmt.Errorf(\"cannot unclaim closed issue %s\", id)\n\t}\n\n\t// Validate: must have an assignee to unclaim\n\tif oldIssue.Assignee == \"\" {\n\t\treturn fmt.Errorf(\"issue %s is not assigned\", id)\n\t}\n\n\t// Validate ownership unless the caller forced the release. Without force, a\n\t// process may only release its own claim. Compared under actorMatches, not\n\t// verbatim, so a caller naming its own identity under a different layer's\n\t// spelling (ga-5ksp5) is not refused as a stranger.\n\tif !force && !actorMatches(oldIssue.Assignee, actor) {\n\t\treturn fmt.Errorf(\"%w: %s is held by %s; coordinate with the holder — pass --force only if their claim is abandoned (crashed agent, expired lease)\",","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/unclaim.go#L25-L61","documentation":"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.","triggerScenarios":"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.","commonSituations":"Typo'd or stale issue ID from a cached handle; database unreachable or locked; concurrent transaction rolled back the row mid-flight.","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."],"exampleFix":"// before\nerr := store.ReleaseIssue(ctx, \"bd-abc\") // stale/garbage id\n// after\niss, err := store.GetIssue(ctx, \"bd-abc\")\nif err != nil { return err } // resolve correct id first\nerr = store.ReleaseIssue(ctx, iss.ID)","handlingStrategy":"try-catch","validationCode":"if _, err := store.GetIssue(ctx, id); err != nil {\n\treturn fmt.Errorf(\"cannot release %s: %w\", id, err)\n}","typeGuard":null,"tryCatchPattern":"if err := store.ReleaseIssue(ctx, id); err != nil {\n\tif isNotFound(err) { /* recreate/skip */ } else if isTransientDBErr(err) { /* retry with backoff */ }\n}","preventionTips":["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."],"tags":["database","transaction","claim-management"],"backgroundTag":"db-read-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}