gastownhall/beads · error

close %s: reload: %w

Error message

close %s: reload: %w

What it means

This error is produced by the issue close use case when the issue was successfully closed (or verified already closed) but the subsequent read-back of the issue record via issueRepo.Get failed. The 'reload:' prefix distinguishes this post-close fetch failure from the close operation itself, which succeeded. The returned error wraps the underlying Get error (e.g. not-found, storage failure) with 'close <id>: reload:'.

Source

Thrown at internal/storage/domain/issue.go:1676

// 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)
	}
	issue, err := u.issueRepo.Get(ctx, id, IssueTableOpts{UseWispsTable: row.IsWisp})
	if err != nil {
		return CloseIssueResult{}, fmt.Errorf("close %s: reload: %w", id, err)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped error: if it is a not-found error, the issue was closed then deleted concurrently — retry or treat the close as effective and re-fetch by listing.
  2. Verify database/storage connectivity and retry the whole CloseIssue call; the close itself is idempotent (AlreadyClosed is reported).
  3. Inspect wisp table routing: if row.IsWisp was true, confirm the wisp still exists in the wisps table or re-run after compaction settles.
  4. Log/report the underlying Get error if it persists, since close state may have changed without a verifiable result.

Example fix

// before: assuming close succeeded means reload will succeed
result, err := u.CloseIssue(ctx, id, params)
// after: handle the reload failure distinctly
closeErr := &CloseReloadError{ID: id, Err: err}
if errors.Is(err, ErrNotFound) {
    // issue closed and concurrently removed; proceed without reload
}
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := usecase.GetIssue(ctx, id); err != nil {
    // cannot even read the issue; close will likely fail or reload fail
    return fmt.Errorf("issue %s unreadable: %w", id, err)
}

Type guard

func hasIssue(issue Issue, ok bool) bool { return ok && issue.ID != "" }

Try / catch

res, err := usecase.CloseIssue(ctx, id, params)
if err != nil {
    if strings.Contains(err.Error(), "reload:") {
        // close committed; handle re-fetch failure separately
        log.Warnf("close of %s committed but reload failed: %v", id, err)
    } else {
        return err
    }
}

Prevention

When it happens

Trigger: Calling CloseIssue (or CloseWisp, which routes through closeWithRowChecked) where u.issueRepo.CloseChecked succeeds but the follow-up u.issueRepo.Get(ctx, id, ...) returns an error — e.g. the row was deleted concurrently between close and reload, or a storage/database error occurs on read, or the wisp-table routing (row.IsWisp || useWisp) points at a table where the row no longer exists.

Common situations: Concurrent deletion of the issue between close and reload; database connectivity blips during the read; wisp-vs-main table inconsistency where the closed row was a wisp that got compacted/merged before Get ran; permissions or driver errors surfacing through the repository layer.

Related errors


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