{"record":{"id":"d0607fbeacebd400","repo":"gastownhall/beads","slug":"close-s-reload-w","errorCode":null,"errorMessage":"close %s: reload: %w","messagePattern":"close (.+?): reload: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/issue.go","lineNumber":1676,"sourceCode":"// CloseWispChecked is the wisp twin of CloseIssueChecked.\nfunc (u *issueUseCaseImpl) CloseWispChecked(ctx context.Context, id string, params CloseIssueParams, actor string, force bool) (CloseIssueResult, error) {\n\treturn u.closeChecked(ctx, id, params, actor, force, true)\n}\n\nfunc (u *issueUseCaseImpl) closeChecked(ctx context.Context, id string, params CloseIssueParams, actor string, force, useWisp bool) (CloseIssueResult, error) {\n\tif id == \"\" {\n\t\treturn CloseIssueResult{}, fmt.Errorf(\"close: id must not be empty\")\n\t}\n\tif actor == \"\" {\n\t\treturn CloseIssueResult{}, fmt.Errorf(\"close: actor must not be empty\")\n\t}\n\trow, err := u.issueRepo.CloseChecked(ctx, id, CloseRowParams{Reason: params.Reason, Session: params.Session}, actor, force)\n\tif err != nil {\n\t\treturn CloseIssueResult{}, fmt.Errorf(\"close %s: %w\", id, err)\n\t}\n\tissue, err := u.issueRepo.Get(ctx, id, IssueTableOpts{UseWispsTable: row.IsWisp || useWisp})\n\tif err != nil {\n\t\treturn CloseIssueResult{}, fmt.Errorf(\"close %s: reload: %w\", id, err)\n\t}\n\treturn CloseIssueResult{Issue: issue, Closed: !row.AlreadyClosed, OpenChildren: row.OpenChildren}, nil\n}\n\nfunc (u *issueUseCaseImpl) close(ctx context.Context, id string, params CloseIssueParams, actor string, useWisp bool) (CloseIssueResult, error) {\n\tif id == \"\" {\n\t\treturn CloseIssueResult{}, fmt.Errorf(\"close: id must not be empty\")\n\t}\n\tif actor == \"\" {\n\t\treturn CloseIssueResult{}, fmt.Errorf(\"close: actor must not be empty\")\n\t}\n\trow, err := u.issueRepo.Close(ctx, id, CloseRowParams{Reason: params.Reason, Session: params.Session}, actor, IssueTableOpts{UseWispsTable: useWisp})\n\tif err != nil {\n\t\treturn CloseIssueResult{}, fmt.Errorf(\"close %s: %w\", id, err)\n\t}\n\tissue, err := u.issueRepo.Get(ctx, id, IssueTableOpts{UseWispsTable: row.IsWisp})\n\tif err != nil {\n\t\treturn CloseIssueResult{}, fmt.Errorf(\"close %s: reload: %w\", id, err)","sourceCodeStart":1658,"sourceCodeEnd":1694,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/issue.go#L1658-L1694","documentation":"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:'.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Verify database/storage connectivity and retry the whole CloseIssue call; the close itself is idempotent (AlreadyClosed is reported).","Inspect wisp table routing: if row.IsWisp was true, confirm the wisp still exists in the wisps table or re-run after compaction settles.","Log/report the underlying Get error if it persists, since close state may have changed without a verifiable result."],"exampleFix":"// before: assuming close succeeded means reload will succeed\nresult, err := u.CloseIssue(ctx, id, params)\n// after: handle the reload failure distinctly\ncloseErr := &CloseReloadError{ID: id, Err: err}\nif errors.Is(err, ErrNotFound) {\n    // issue closed and concurrently removed; proceed without reload\n}","handlingStrategy":"try-catch","validationCode":"if _, err := usecase.GetIssue(ctx, id); err != nil {\n    // cannot even read the issue; close will likely fail or reload fail\n    return fmt.Errorf(\"issue %s unreadable: %w\", id, err)\n}","typeGuard":"func hasIssue(issue Issue, ok bool) bool { return ok && issue.ID != \"\" }","tryCatchPattern":"res, err := usecase.CloseIssue(ctx, id, params)\nif err != nil {\n    if strings.Contains(err.Error(), \"reload:\") {\n        // close committed; handle re-fetch failure separately\n        log.Warnf(\"close of %s committed but reload failed: %v\", id, err)\n    } else {\n        return err\n    }\n}","preventionTips":["Avoid concurrent delete-and-close of the same issue; serialize mutations per ID.","Wrap close+reload in retries for transient storage errors.","Monitor wisp compaction windows if you close wisps frequently.","Always inspect the wrapped cause with errors.Is/errors.As rather than string matching where possible."],"tags":["storage","close-issue","reload","wrapped-error"],"backgroundTag":"post-write-reload-failure","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}