gastownhall/beads · error

%w: issue %s

Error message

%w: issue %s

What it means

When neither the wisp read nor the durable GetIssue finds the issue (ErrNotFound or no-rows), operationIssue returns publicops.ErrNotFound wrapped with the issue ID. It is the library's canonical 'issue does not exist' signal for update/close/reopen paths.

Source

Thrown at internal/storage/uow/issue_operations.go:508

// operationIssue resolves id to the row an operation is about. Both planes are
// searched unless issuePlaneOnly restricts it, in which case a wisp id is a
// miss rather than an ephemeral row to operate on. Every call runs inside the
// operation's own transaction.
func operationIssue(ctx context.Context, uw UnitOfWork, id string, issuePlaneOnly bool) (*types.Issue, bool, error) {
	if !issuePlaneOnly {
		issue, err := uw.IssueUseCase().GetWisp(ctx, id)
		if err == nil && issue != nil {
			return issue, true, nil
		}
		if err != nil && !errors.Is(err, publicops.ErrNotFound) && !dberrors.IsNoRows(err) {
			return nil, false, fmt.Errorf("read wisp %s: %w", id, err)
		}
	}
	issue, err := uw.IssueUseCase().GetIssue(ctx, id)
	if err != nil {
		if errors.Is(err, publicops.ErrNotFound) || dberrors.IsNoRows(err) {
			return nil, false, fmt.Errorf("%w: issue %s", publicops.ErrNotFound, id)
		}
		return nil, false, err
	}
	if issue == nil {
		return nil, false, fmt.Errorf("%w: issue %s", publicops.ErrNotFound, id)
	}
	return issue, false, nil
}

func validationError(err error) error {
	if errors.Is(err, publicops.ErrValidation) {
		return err
	}
	return fmt.Errorf("%w: %w", publicops.ErrValidation, err)
}

func validateUpdateRequest(request publicops.UpdateRequest) error {
	if request.Actor == "" || request.IssueID == "" {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Verify the issue ID is correct (list issues to confirm it exists)
  2. Check you are pointed at the right database (path, branch, remote)
  3. If the ID came from another system, re-fetch it rather than caching
  4. Handle publicops.ErrNotFound via errors.Is in the caller instead of retrying

Example fix

// before: string matching on the error
if strings.Contains(err.Error(), "not found") { ... }
// after
if errors.Is(err, publicops.ErrNotFound) {
    // handle missing issue
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Confirm existence before update/close/reopen
if _, err := uc.GetIssue(ctx, id); errors.Is(err, publicops.ErrNotFound) { return fmt.Errorf("issue %s missing", id) }

Type guard

func isIssueNotFound(err error) bool {
    return errors.Is(err, publicops.ErrNotFound) || dberrors.IsNoRows(err)
}

Try / catch

if err := uc.Update(ctx, req); err != nil {
    if isIssueNotFound(err) {
        // issue absent: skip, or recreate
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Updating, closing, reopening, or hydrating an issue ID that does not exist: GetIssue returned ErrNotFound or dberrors.IsNoRows(err) is true, or the wisp read already said not-found.

Common situations: Typo'd issue key (e.g. bd-1234 vs bd-1243), issue already deleted by another process, operating against a different database/branch than the one holding the issue, stale ID from an old export.

Related errors


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