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
- Verify the issue ID is correct (list issues to confirm it exists)
- Check you are pointed at the right database (path, branch, remote)
- If the ID came from another system, re-fetch it rather than caching
- 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
- Fetch issue IDs from list output instead of hand-typing keys
- Confirm you are pointed at the correct database/branch
- Never cache issue IDs across exports or DB resets
- Use errors.Is against publicops.ErrNotFound, never string matching
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.