gastownhall/beads · error
issue not found: %s
Error message
issue not found: %s
What it means
The rename UPDATE executed successfully but affected zero rows, meaning no issue with oldID existed. The library reports this as a plain error ("issue not found: %s") so callers can distinguish a missing source issue from a driver failure. The transaction aborts with no changes.
Source
Thrown at internal/storage/issueops/bulk_ops.go:304
if err := RecordDepEventInTx(ctx, tx, EventDepAdd, source, edge.kind, target, edge.metadata, actor); err != nil {
return err
}
}
return nil
}
func updateIssueIDInTx(ctx context.Context, tx *sql.Tx, oldID, newID string, issue *types.Issue, actor string) error {
now := time.Now().UTC()
result, err := tx.ExecContext(ctx, `
UPDATE issues
SET id = ?, title = ?, description = ?, design = ?, acceptance_criteria = ?, notes = ?, updated_at = ?
WHERE id = ?
`, newID, issue.Title, issue.Description, issue.Design, issue.AcceptanceCriteria, issue.Notes, now, oldID)
if err != nil {
return fmt.Errorf("update issue ID: %w", err)
}
if rows, _ := result.RowsAffected(); rows == 0 {
return fmt.Errorf("issue not found: %s", oldID)
}
if err := UpdateIssueIDInDependenciesInTx(ctx, tx, oldID, newID); err != nil {
return err
}
// A live lease follows its issue across the rename.
if _, err := tx.ExecContext(ctx,
`UPDATE leases SET issue_id = ? WHERE issue_id = ?`, newID, oldID); err != nil {
return fmt.Errorf("rename lease row: %w", err)
}
return InsertDerivedEvent(ctx, tx, "events", AuxEvent{
IssueID: newID,
EventType: "renamed",
Actor: actor,
OldValue: str(oldID),
NewValue: str(newID),View on GitHub (pinned to 71377f2769)
Solutions
- Fetch the issue by oldID first and handle the not-found case before renaming.
- Confirm the correct ID (check for typos, case, prefix) via `bd show <id>` or an equivalent query.
- If the target is a wisp, use the wisp path (the library routes automatically only when the row exists).
- Sync/pull latest data so the issue exists locally, then retry.
Example fix
// before
store.UpdateIssueID(ctx, oldID, newID, issue, actor) // panics with 'issue not found' if stale
// after
if _, err := store.GetIssue(ctx, oldID); err != nil {
return fmt.Errorf("skip rename, source missing: %w", err)
}
store.UpdateIssueID(ctx, oldID, newID, issue, actor) Defensive patterns
Strategy: validation
Validate before calling
func mustExist(ctx context.Context, store Store, id string) error {
iss, err := store.GetIssue(ctx, id)
if err != nil { return err }
if iss == nil { return fmt.Errorf("issue %s not found; fetch latest before renaming", id) }
return nil
} Try / catch
err := store.UpdateIssueID(ctx, oldID, newID, issue, actor)
if err != nil && strings.HasPrefix(err.Error(), "issue not found:") {
// stale reference: re-sync and re-check
return handleStaleID(err, oldID)
} Prevention
- Fetch the issue immediately before renaming instead of using cached IDs.
- Sync latest state before bulk ID operations.
- Distinguish wisp IDs from issue IDs in your tooling (different tables).
When it happens
Trigger: Calling UpdateIssueIDInTx/UpdateIssueID with an oldID that is not present in the issues table — already deleted, wrong prefix/format, or the issue is actually a wisp (lives in the wisps table, not issues).
Common situations: Stale client cache referencing an issue deleted by another process; ID case-sensitivity or typo (bd-12 vs bd-21); calling rename on a wisp ID instead of a real issue; multi-machine sync where the issue hasn't been pulled yet.
Related errors
- wisp not found: %s
- %w: issue %s
- db: Update %s: %w
- db: Claim %s: read old issue: %w
- capture dependency edges for rename %s -> %s: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/50461479defa226c.
Report an issue: GitHub.