gastownhall/beads · error
%w: issue %s
Error message
%w: issue %s
What it means
resolveCommentPlaneInTx refuses to add a comment when the IssueID names neither an existing issue nor an active wisp in the same transaction. The refusal is deliberately typed — storage.ErrNotFound wrapped as "%w: issue %s" — so callers can classify with errors.Is instead of parsing prose from the insert itself. The existence probe runs in the caller's transaction to prevent a TOCTOU race where the anchor is deleted between the check and the insert.
Source
Thrown at internal/storage/issueops/commenter.go:80
// refusing an id that names neither an issue nor a wisp.
//
// The existence probe is here rather than left to the insert's own so the
// refusal is TYPED: AddIssueCommentInTx reports a missing anchor as prose, and
// a caller of this role classifies with errors.Is. It resolves the plane in
// the same transaction the insert runs in, so a comment cannot land on a row
// an earlier read saw and this one did not.
//
//nolint:gosec // G201: issueTable comes from WispTableRouting ("issues" or "wisps")
func resolveCommentPlaneInTx(ctx context.Context, tx *sql.Tx, issueID string) (string, error) {
isWisp := IsActiveWispInTx(ctx, tx, issueID)
issueTable, _, _, _ := WispTableRouting(isWisp)
var exists bool
if err := tx.QueryRowContext(ctx,
fmt.Sprintf(`SELECT EXISTS(SELECT 1 FROM %s WHERE id = ?)`, issueTable), issueID).Scan(&exists); err != nil {
return "", fmt.Errorf("check issue existence: %w", err)
}
if !exists {
return "", fmt.Errorf("%w: issue %s", storage.ErrNotFound, issueID)
}
if isWisp {
return "wisp_comments", nil
}
return "comments", nil
}
View on GitHub (pinned to 71377f2769)
Solutions
- Verify the issue ID with bd show <id> (or an equivalent lookup) before commenting; fix typos in the ID.
- Handle errors.Is(err, storage.ErrNotFound) explicitly and skip/report the missing anchor rather than retrying.
- Re-sync or re-export your local data if the issue legitimately exists upstream but not locally.
Example fix
// before
if err := store.AddComment(ctx, req); err != nil {
return err // blind failure
}
// after
if err := store.AddComment(ctx, req); err != nil {
if errors.Is(err, storage.ErrNotFound) {
log.Warnf("issue %s gone, skipping comment", req.IssueID)
return nil
}
return err
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check (best effort; the typed error is authoritative)
if _, err := store.GetIssue(ctx, issueID); err != nil {
return fmt.Errorf("cannot comment: %w", err)
} Type guard
func isMissingIssue(err error) bool {
return errors.Is(err, storage.ErrNotFound)
} Try / catch
if err := store.AddComment(ctx, req); err != nil {
if errors.Is(err, storage.ErrNotFound) {
log.Warnf("issue %s not found; skipping comment", req.IssueID)
return nil
}
return err
} Prevention
- Resolve issue IDs from bd's own listing rather than free-text/log scraping.
- Handle ErrNotFound as an expected outcome in automation (skip, don't crash).
- Re-sync local data before batch operations that reference upstream IDs.
When it happens
Trigger: Calling AddComment with a typo'd or deleted issue ID (bd comment bd-9999 "x"); commenting on an issue another process closed/deleted moments earlier; using an ID from a stale local export after a sync removed the issue.
Common situations: Automation following issue IDs scraped from logs or old tickets; scripts with hardcoded IDs after a database reset (bd init in a fresh clone); race between a cleanup job deleting wisps and a bot adding comments.
Related errors
- no store is open for this workspace
- load wisp comments: %w
- molecule %s not found: %w
- issue %s not found
- epic '%s' not found
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/e5e31d0b388ac5db.
Report an issue: GitHub.