{"record":{"id":"228c105ea5aad374","repo":"gastownhall/beads","slug":"w-issue-s-228c10","errorCode":null,"errorMessage":"%w: issue %s","messagePattern":"%w: issue (.+?)","errorType":"exception","errorClass":"storage.ErrNotFound","httpStatus":null,"severity":"info","filePath":"internal/storage/issueops/get_issue.go","lineNumber":33,"sourceCode":"// GetIssueInTx retrieves a single issue by ID within an existing transaction,\n// including its labels. Automatically routes to the wisps/wisp_labels tables\n// if the ID is an active wisp. Returns storage.ErrNotFound (wrapped) if the\n// issue does not exist in either table.\nfunc GetIssueInTx(ctx context.Context, tx DBTX, id string) (*types.Issue, error) {\n\tissue, err := getIssueFromTableInTx(ctx, tx, \"issues\", \"labels\", id)\n\tif err == nil {\n\t\treturn issue, nil\n\t}\n\tif !errors.Is(err, storage.ErrNotFound) {\n\t\treturn nil, err\n\t}\n\n\tissue, err = getIssueFromTableInTx(ctx, tx, \"wisps\", \"wisp_labels\", id)\n\tif err == nil {\n\t\treturn issue, nil\n\t}\n\tif errors.Is(err, storage.ErrNotFound) {\n\t\treturn nil, fmt.Errorf(\"%w: issue %s\", storage.ErrNotFound, id)\n\t}\n\treturn nil, err\n}\n\n// missingOptionalIssueTable reports whether err is the absence of the optional\n// issue plane the hydration query just read. The hydration FROM clause also\n// carries sqlbuild.LeaseJoin, so a blanket table-not-exist check here folds a\n// missing leases table into \"row absent\" — a wrong answer, not an empty one.\nfunc missingOptionalIssueTable(err error, issueTable string) bool {\n\treturn optionalBlockedTable(issueTable) && dberrors.IsMissingTable(err, issueTable)\n}\n\nfunc getIssueFromTableInTx(ctx context.Context, tx DBTX, issueTable, labelTable, id string) (*types.Issue, error) {\n\t//nolint:gosec // G201: issueTable is a hardcoded literal supplied by GetIssueInTx (\"issues\" or \"wisps\")\n\trow := tx.QueryRowContext(ctx, fmt.Sprintf(`SELECT %s FROM %s %s WHERE id = ?`,\n\t\tIssueSelectColumns, issueTable, sqlbuild.LeaseJoin(issueTable)), id)\n\tissue, err := ScanIssueFrom(row)\n\tif err == sql.ErrNoRows || missingOptionalIssueTable(err, issueTable) {","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/get_issue.go#L15-L51","documentation":"GetIssueInTx looks up the ID in the issues table, then in wisps; if both return storage.ErrNotFound, it wraps storage.ErrNotFound with the issue ID. This is the canonical not-found signal — detect it with errors.Is. It means no issue or wisp with that ID exists in this database.","triggerScenarios":"Calling GetIssueInTx (directly or via ClaimIssueInTx, ClaimReadyIssueInTx, GetNewlyUnblockedByCloseInTx, buildDependencyTreeInTx, ExecuteUpdate) with an ID that exists in neither issues nor wisps.","commonSituations":"Typo'd or truncated issue ID; operating on a different database/clone than where the issue was created; issue deleted by another process; prefix-matched ID that resolved incorrectly.","solutions":["Verify the ID with bd show <id> or by listing issues before the lookup","Confirm you are connected to the database where the issue was created","Branch on errors.Is(err, storage.ErrNotFound) to handle absence gracefully","Re-create the issue if it was legitimately deleted"],"exampleFix":"// before\nissue, err := issueops.GetIssueInTx(ctx, tx, id) // hard-fails on unknown ID\n// after\nissue, err := issueops.GetIssueInTx(ctx, tx, id)\nif errors.Is(err, storage.ErrNotFound) {\n    return fmt.Errorf(\"issue %q does not exist in this database\", id)\n}\nif err != nil {\n    return err\n}","handlingStrategy":"type-guard","validationCode":"// pre-check existence without labels/lease join\nvar exists bool\nerr := tx.QueryRowContext(ctx, `SELECT COUNT(*) > 0 FROM issues WHERE id = ?`, id).Scan(&exists)","typeGuard":"func isIssueNotFound(err error) bool {\n    return errors.Is(err, storage.ErrNotFound)\n}","tryCatchPattern":"issue, err := issueops.GetIssueInTx(ctx, tx, id)\nif isIssueNotFound(err) {\n    return fmt.Errorf(\"issue %q not found; run 'bd list' to see available IDs\", id)\n}\nif err != nil {\n    return err\n}","preventionTips":["Always branch on errors.Is(err, storage.ErrNotFound) before generic handling","Confirm the database/clone you are querying is the one where the ID was created","Use full IDs (or validate prefix expansion) instead of hand-typed partial IDs"],"tags":["not-found","issues","go"],"backgroundTag":"issue-not-found","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}