{"record":{"id":"f8f81e89310d870b","repo":"gastownhall/beads","slug":"reading-custom-statuses-for-wisp-age-gc-w","errorCode":null,"errorMessage":"reading custom statuses for wisp age GC: %w","messagePattern":"reading custom statuses for wisp age GC: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/bd/wisp.go","lineNumber":688,"sourceCode":"\tprotected := make(map[types.Status]bool)\n\tfor _, s := range []types.Status{\n\t\ttypes.StatusOpen,\n\t\ttypes.StatusInProgress,\n\t\ttypes.StatusBlocked,\n\t\ttypes.StatusClosed,\n\t\ttypes.StatusDeferred,\n\t\ttypes.StatusPinned,\n\t\ttypes.StatusHooked,\n\t} {\n\t\tswitch types.BuiltInStatusCategory(s) {\n\t\tcase types.CategoryWIP, types.CategoryFrozen:\n\t\t\tprotected[s] = true\n\t\t}\n\t}\n\n\tcustomStatuses, err := r.GetCustomStatusesDetailed(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"reading custom statuses for wisp age GC: %w\", err)\n\t}\n\tfor _, cs := range customStatuses {\n\t\tswitch cs.Category {\n\t\tcase types.CategoryWIP, types.CategoryFrozen:\n\t\t\tprotected[types.Status(cs.Name)] = true\n\t\t}\n\t}\n\treturn protected, nil\n}\n\n// isProtectedWisp reports whether a wisp is live work that age-based GC must\n// never reclaim. A wisp is protected if it is explicitly pinned, if it is\n// blocked on an open dependency (blockedSet, derived from is_blocked), or if\n// its status falls in a protected category. Reclaiming any of these\n// mid-execution destroys active molecules (GH#4394).\n//\n// Named isProtectedWisp rather than isActiveWisp to avoid confusion with\n// (*DoltStore).isActiveWisp in internal/storage/dolt, which is in this same","sourceCodeStart":670,"sourceCodeEnd":706,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/wisp.go#L670-L706","documentation":"During wisp age-based garbage collection, protectedWispStatuses builds the set of statuses that must not be collected, including custom statuses categorized as WIP or Frozen. If reading the custom status definitions from the repository fails, the GC cannot know what is protected and aborts with this wrapped error rather than risk deleting wisps it should preserve.","triggerScenarios":"findAbandonedWisps calls r.GetCustomStatusesDetailed(ctx) and the underlying store/driver returns an error (database unavailable, corrupted status config, driver failure).","commonSituations":"Database locked or unreachable during GC runs in CI; Dolt/storage driver failures; corrupted custom-status configuration in .beads metadata.","solutions":["Check database connectivity and retry the GC command","Inspect/repair custom status configuration (`bd statuses` or equivalent)","Investigate the wrapped inner error (%w) for the root cause"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if err := pingStore(ctx, r); err != nil {\n    return fmt.Errorf(\"store unreachable; defer wisp GC: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"if err := runWispGC(ctx); err != nil {\n    var wrapped string = err.Error()\n    if strings.Contains(wrapped, \"reading custom statuses for wisp age GC\") {\n        // retry with backoff or skip GC this cycle; never GC without the protected set\n    }\n}","preventionTips":["Run GC only when the database is healthy/reachable","Always load the protected-status set before any deletion pass","Treat custom-status read failures as fatal to GC, never as empty"],"tags":["gc","storage","custom-statuses"],"backgroundTag":"storage-read-failure","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}