{"record":{"id":"6f3b300420e4d0b9","repo":"gastownhall/beads","slug":"wakeexpireddefers-w","errorCode":null,"errorMessage":"WakeExpiredDefers: %w","messagePattern":"WakeExpiredDefers: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/issue.go","lineNumber":1929,"sourceCode":"\tif id == \"\" {\n\t\treturn fmt.Errorf(\"Heartbeat: id must not be empty\")\n\t}\n\tif err := u.issueRepo.HeartbeatIssue(ctx, id, actor); err != nil {\n\t\treturn fmt.Errorf(\"Heartbeat: %w\", err)\n\t}\n\treturn nil\n}\n\n// WakeExpiredDefers returns every expired DATED defer to open (see\n// issueops.WakeExpiredDefersInTx) and reports how many permanent issues and\n// wisps woke. The caller owns persistence: commit with a wake message iff\n// issues > 0, and with the ephemeral plain-COMMIT form iff only wisps woke\n// (wisp tables are dolt_ignored, so their wake needs a SQL commit but must\n// mint no version commit).\nfunc (u *issueUseCaseImpl) WakeExpiredDefers(ctx context.Context) (issues, wisps int, err error) {\n\tissues, wisps, err = u.issueRepo.WakeExpiredDefers(ctx)\n\tif err != nil {\n\t\treturn 0, 0, fmt.Errorf(\"WakeExpiredDefers: %w\", err)\n\t}\n\treturn issues, wisps, nil\n}\n\nfunc (u *issueUseCaseImpl) ReclaimExpiredLeases(ctx context.Context, olderThan time.Duration, filter types.ReclaimFilter, actor string) ([]types.ReclaimedLease, error) {\n\tout, err := u.issueRepo.ReclaimExpiredLeases(ctx, olderThan, filter, actor)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"ReclaimExpiredLeases: %w\", err)\n\t}\n\treturn out, nil\n}\n","sourceCodeStart":1911,"sourceCodeEnd":1941,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/issue.go#L1911-L1941","documentation":"Wrapped error from WakeExpiredDefers: the repo call that flips expired DATED defers back to open failed and is prefixed with \"WakeExpiredDefers: \". The method returns counts of woken permanent issues and wisps; on this error both counts are zeroed and the wake did not complete. The correct transaction form depends on whether permanent issues or only wisps woke.","triggerScenarios":"Calling WakeExpiredDefers(ctx) when issueRepo.WakeExpiredDefers fails: database unavailable, transaction/commit error (e.g. wrong tx form when permanent issues are involved and a version commit is required), or SQL error in the wake scan.","commonSituations":"Periodic defer-wakeup jobs (cron/daemon) hitting a DB outage; concurrent writers conflicting with the wake transaction; misconfigured ephemeral-vs-versioned commit mode.","solutions":["Unwrap the error to find the underlying repository/transaction cause","Check database connectivity and retry the wake sweep","Ensure the sweep runs with the correct tx form: version-commit when issues > 0, plain ephemeral commit when only wisps woke","Look for lock contention from concurrent bd processes and serialize the sweep"],"exampleFix":"// before\ni, w, err := uc.WakeExpiredDefers(ctx)\nif err != nil { return err }\n// after\ni, w, err := uc.WakeExpiredDefers(ctx)\nif err != nil {\n\tlog.Printf(\"defer wake failed, will retry: %v\", err)\n\treturn retryWithBackoff(func() error { _, _, err = uc.WakeExpiredDefers(ctx); return err })\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"issues, wisps, err := uc.WakeExpiredDefers(ctx)\nif err != nil {\n\treturn retryWithBackoff(func() error {\n\t\tissues, wisps, err = uc.WakeExpiredDefers(ctx)\n\t\treturn err\n\t})\n}","preventionTips":["Schedule the wake sweep with a single owner (lock or leader election) to avoid conflicts","Run DB health checks before each sweep","Log the wrapped cause to distinguish tx-form problems from outages","Retry with backoff — the sweep is idempotent for remaining expired defers"],"tags":["storage","error-wrapping","defers","transaction"],"backgroundTag":"repo-error-wrapping","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}