gastownhall/beads · error
close %s: %w
Error message
close %s: %w
What it means
Wraps the underlying error from issueRepo.CloseChecked — the actual close-write against the issues/wisps table failed. The ID is interpolated for context and the storage error is chained as %w. This is the generic wrapper for storage-level close failures, distinct from the empty-id/actor guards and from the later reload failure.
Source
Thrown at internal/storage/domain/issue.go:1672
func (u *issueUseCaseImpl) CloseIssueChecked(ctx context.Context, id string, params CloseIssueParams, actor string, force bool) (CloseIssueResult, error) {
return u.closeChecked(ctx, id, params, actor, force, false)
}
// CloseWispChecked is the wisp twin of CloseIssueChecked.
func (u *issueUseCaseImpl) CloseWispChecked(ctx context.Context, id string, params CloseIssueParams, actor string, force bool) (CloseIssueResult, error) {
return u.closeChecked(ctx, id, params, actor, force, true)
}
func (u *issueUseCaseImpl) closeChecked(ctx context.Context, id string, params CloseIssueParams, actor string, force, useWisp bool) (CloseIssueResult, error) {
if id == "" {
return CloseIssueResult{}, fmt.Errorf("close: id must not be empty")
}
if actor == "" {
return CloseIssueResult{}, fmt.Errorf("close: actor must not be empty")
}
row, err := u.issueRepo.CloseChecked(ctx, id, CloseRowParams{Reason: params.Reason, Session: params.Session}, actor, force)
if err != nil {
return CloseIssueResult{}, fmt.Errorf("close %s: %w", id, err)
}
issue, err := u.issueRepo.Get(ctx, id, IssueTableOpts{UseWispsTable: row.IsWisp || useWisp})
if err != nil {
return CloseIssueResult{}, fmt.Errorf("close %s: reload: %w", id, err)
}
return CloseIssueResult{Issue: issue, Closed: !row.AlreadyClosed, OpenChildren: row.OpenChildren}, nil
}
func (u *issueUseCaseImpl) close(ctx context.Context, id string, params CloseIssueParams, actor string, useWisp bool) (CloseIssueResult, error) {
if id == "" {
return CloseIssueResult{}, fmt.Errorf("close: id must not be empty")
}
if actor == "" {
return CloseIssueResult{}, fmt.Errorf("close: actor must not be empty")
}
row, err := u.issueRepo.Close(ctx, id, CloseRowParams{Reason: params.Reason, Session: params.Session}, actor, IssueTableOpts{UseWispsTable: useWisp})
if err != nil {
return CloseIssueResult{}, fmt.Errorf("close %s: %w", id, err)View on GitHub (pinned to 71377f2769)
Solutions
- Read the wrapped %w error: if not-found, verify the ID exists (bd show <id>).
- If already-closed, either treat as success in your flow or pass force=true.
- Check DB connectivity/locks and retry transient errors.
- Use the matching table variant — a wisp ID may need CloseWisp* rather than CloseIssue* depending on your lookup path.
Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check existence and state before closing
issue, err := uc.GetIssue(ctx, id)
if err != nil {
return fmt.Errorf("issue %s not found: %w", id, err)
}
if issue.Status == StatusClosed && !force {
return fmt.Errorf("issue %s already closed; pass force to no-op", id)
} Try / catch
if err != nil {
switch {
case errors.Is(err, ErrNotFound):
return fmt.Errorf("close %s: no such issue; verify ID with bd show", id)
case errors.Is(err, ErrAlreadyClosed):
return nil // idempotent close
case isTransientStorageErr(err):
return retryWithBackoff(closeOp)
default:
return fmt.Errorf("close %s: %w", id, err)
}
} Prevention
- Verify issue IDs exist (bd show) before closing in automation.
- Design closes as idempotent — treat already-closed as success or use force.
- Check DB health (bd doctor) before bulk close operations.
- Use short, retriable context timeouts around close writes.
When it happens
Trigger: CloseChecked call fails: issue not found in either table, already closed without force, DB write error, permission/driver error, context canceled during the write.
Common situations: Typo in issue ID (bd-9999 doesn't exist); trying to close an already-closed issue without force; database locked or offline; concurrent sync conflict.
Related errors
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/a97f3c23d8cccf0b.
Report an issue: GitHub.