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

  1. Fetch the issue by oldID first and handle the not-found case before renaming.
  2. Confirm the correct ID (check for typos, case, prefix) via `bd show <id>` or an equivalent query.
  3. If the target is a wisp, use the wisp path (the library routes automatically only when the row exists).
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/50461479defa226c. Report an issue: GitHub.