gastownhall/beads · error

reopen %s: reload: %w

Error message

reopen %s: reload: %w

What it means

Reload-failure variant for reopen: issueRepo.Reopen succeeded but the subsequent issueRepo.Get to return the freshly reopened issue failed. The 'reopen <id>: reload:' wrapper distinguishes the failed read-back from a failed reopen.

Source

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

func (u *issueUseCaseImpl) ReopenWisp(ctx context.Context, id string, params ReopenIssueParams, actor string) (ReopenIssueResult, error) {
	return u.reopen(ctx, id, params, actor, true)
}

func (u *issueUseCaseImpl) reopen(ctx context.Context, id string, params ReopenIssueParams, actor string, useWisp bool) (ReopenIssueResult, error) {
	if id == "" {
		return ReopenIssueResult{}, fmt.Errorf("reopen: id must not be empty")
	}
	if actor == "" {
		return ReopenIssueResult{}, fmt.Errorf("reopen: actor must not be empty")
	}
	row, err := u.issueRepo.Reopen(ctx, id, ReopenRowParams{Reason: params.Reason}, actor, IssueTableOpts{UseWispsTable: useWisp})
	if err != nil {
		return ReopenIssueResult{}, fmt.Errorf("reopen %s: %w", id, err)
	}
	issue, err := u.issueRepo.Get(ctx, id, IssueTableOpts{UseWispsTable: row.IsWisp})
	if err != nil {
		return ReopenIssueResult{}, fmt.Errorf("reopen %s: reload: %w", id, err)
	}
	return ReopenIssueResult{
		Issue:    issue,
		Reopened: row.Updated,
	}, nil
}

func (u *issueUseCaseImpl) ClaimIssueIfOpen(ctx context.Context, id, actor string) (ClaimResult, error) {
	return u.claim(ctx, id, actor, false)
}

func (u *issueUseCaseImpl) ClaimWispIfOpen(ctx context.Context, id, actor string) (ClaimResult, error) {
	return u.claim(ctx, id, actor, true)
}

func (u *issueUseCaseImpl) CountOpenChildren(ctx context.Context, id string) (int, error) {
	return u.countOpenChildren(ctx, id, false)
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Retry; the reopen is committed and effectively idempotent.
  2. If the cause is not-found, search/list to find which table now holds the row.
  3. Check storage health and driver errors in the wrapped cause.
  4. Skip the reload and treat Reopened: true as authoritative if eventual consistency suffices.

Example fix

// before
res, err := usecase.ReopenIssue(ctx, id, params, actor)
// after
if err != nil && strings.Contains(err.Error(), "reload") {
    issue, ferr := usecase.GetIssue(ctx, id) // separate re-fetch
    _ = issue; _ = ferr
}
Defensive patterns

Strategy: try-catch

Validate before calling

if _, err := usecase.GetIssue(ctx, id); err != nil {
    return fmt.Errorf("issue %s unreadable before reopen", id)
}

Type guard

func isReloadErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), ": reload: ")
}

Try / catch

res, err := usecase.ReopenIssue(ctx, id, params, actor)
if isReloadErr(err) {
    issue, ferr := usecase.GetIssue(ctx, id)
    if ferr != nil {
        log.Warnf("reload after reopen failed: %v", ferr)
    }
    return nil // reopen committed
}

Prevention

When it happens

Trigger: ReopenIssue flow where Reopen commits and u.issueRepo.Get(ctx, id, IssueTableOpts{UseWispsTable: row.IsWisp}) then fails — concurrent delete/move of the row, storage read error, or wisp-table mismatch between write and read.

Common situations: Wisp compaction moving/removing the row between the two calls; transient DB read failure; concurrent agent deleting the issue after it was reopened; replica lag.

Related errors


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