gastownhall/beads · warning · storage.ErrNotFound
%w: issue %s
Error message
%w: issue %s
What it means
This error is a wrapped storage.ErrNotFound returned when a wisp lookup by ID finds no row in the underlying table. scanIssueFromTable runs a SELECT ... WHERE id = ? and when sql.ErrNoRows comes back it wraps storage.ErrNotFound with the offending issue ID so callers can match with errors.Is. It is the library's canonical 'no such issue/wisp' signal, not a database failure.
Source
Thrown at internal/storage/dolt/wisps.go:37
// insertIssueIntoTable delegates to the shared issueops.InsertIssueIntoTable.
func insertIssueIntoTable(ctx context.Context, tx *sql.Tx, table string, issue *types.Issue) error {
return issueops.InsertIssueIntoTable(ctx, tx, table, issue)
}
// scanIssueFromTable scans a single issue from the specified table.
//
//nolint:gosec // G201: table is a hardcoded constant ("issues" or "wisps")
func scanIssueFromTable(ctx context.Context, db *sql.DB, table, id string) (*types.Issue, error) {
row := db.QueryRowContext(ctx, fmt.Sprintf(`
SELECT %s
FROM %s %s
WHERE id = ?
`, issueSelectColumns, table, sqlbuild.LeaseJoin(table)), id)
issue, err := scanIssueFrom(row)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("%w: issue %s", storage.ErrNotFound, id)
}
if err != nil {
return nil, fmt.Errorf("failed to get issue from %s: %w", table, err)
}
return issue, nil
}
// generateIssueIDInTable generates a unique ID, checking for collisions
// in the specified table. Supports counter mode for non-ephemeral issues.
//
//nolint:gosec // G201: table is a hardcoded constant
func generateIssueIDInTable(ctx context.Context, tx *sql.Tx, table, prefix string, issue *types.Issue, actor string) (string, error) {
// Counter mode only applies to the issues table (not wisps).
if table == "issues" {
counterMode, err := isCounterModeTx(ctx, tx)
if err != nil {
return "", err
}View on GitHub (pinned to 71377f2769)
Solutions
- Verify the issue ID exists (e.g. bd show <id> or a SELECT on the wisps/issues table) and fix typos
- Check errors.Is(err, storage.ErrNotFound) in the caller and handle it as an expected 'missing' path rather than a crash
- Confirm you are pointed at the correct database (BEADS_DB / Dolt database) and table scope
- If the item should exist, check whether it was closed/deleted or demoted and look up the new ID
Example fix
// before
issue, err := store.GetIssue(ctx, id)
if err != nil {
return err
}
// after
issue, err := store.GetIssue(ctx, id)
if err != nil {
if errors.Is(err, storage.ErrNotFound) {
return fmt.Errorf("issue %s does not exist", id)
}
return err
} Defensive patterns
Strategy: type-guard
Validate before calling
var exists bool
err := db.QueryRow(`SELECT COUNT(*) FROM wisps WHERE id = ?`, id).Scan(&exists)
if err == nil && !exists {
return fmt.Errorf("issue %s not found", id)
} Type guard
func IsNotFound(err error) bool {
return errors.Is(err, storage.ErrNotFound)
} Try / catch
issue, err := store.GetIssue(ctx, id)
switch {
case errors.Is(err, storage.ErrNotFound):
// handle missing record gracefully
return nil
case err != nil:
return fmt.Errorf("lookup %s: %w", id, err)
} Prevention
- Always match with errors.Is(err, storage.ErrNotFound), never string comparison
- Validate IDs against a listing before mutating or deep-processing them
- Refresh stale IDs after close/demote operations instead of caching them
- Confirm database/scope configuration before lookups in scripts
When it happens
Trigger: Calling getWisp (directly or via GetIssue/isActiveWisp) with an ID that does not exist in the wisps table; the ID was deleted, demoted/renamed, belongs to the wrong table, or was mistyped.
Common situations: A caller holds a stale ID after the wisp was closed and deleted; scripts referencing an issue from another repo/database; racing with demoteToWispInTx that moved the row; typos in hand-written IDs from CLI usage or automation.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/a3591b89aad3c5bf.
Report an issue: GitHub.