{"record":{"id":"27bdacc4b9b0c068","repo":"gastownhall/beads","slug":"reparent-list-current-parent-w","errorCode":null,"errorMessage":"reparent: list current parent: %w","messagePattern":"reparent: list current parent: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/dependency.go","lineNumber":395,"sourceCode":"\treturn u.reparent(ctx, childWispID, newParentID, actor, true)\n}\n\nfunc (u *dependencyUseCaseImpl) reparent(ctx context.Context, childID, newParentID, actor string, useWisp bool) error {\n\tif childID == \"\" {\n\t\treturn fmt.Errorf(\"reparent: childID must not be empty\")\n\t}\n\tif childID == newParentID {\n\t\treturn fmt.Errorf(\"reparent: %s cannot be its own parent\", childID)\n\t}\n\n\topts := DepInsertOpts{UseWispsTable: useWisp}\n\tres, err := u.depRepo.ListByIssueIDs(ctx, []string{childID}, DepListOpts{\n\t\tTypes:         []types.DependencyType{types.DepParentChild},\n\t\tDirection:     DepDirectionOut,\n\t\tUseWispsTable: useWisp,\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"reparent: list current parent: %w\", err)\n\t}\n\n\t// A child can carry MORE THAN ONE parent-child edge — Create accepts\n\t// CreateRequest.ParentID and an explicit parent-child entry in\n\t// Dependencies in the same request — so this is a set replacement, not a\n\t// swap of one edge. Diffing the whole existing set against the target set\n\t// is the same rule the store-backed backends apply in\n\t// issueops.ApplyParentPatch; that body cannot be called from here because\n\t// internal/storage/issueops imports this package (bd-yby99.26).\n\texisting := map[string]struct{}{}\n\tfor _, dep := range res.Outgoing[childID] {\n\t\tif dep.Type == types.DepParentChild {\n\t\t\texisting[dep.DependsOnID] = struct{}{}\n\t\t}\n\t}\n\ttarget := map[string]struct{}{}\n\tif newParentID != \"\" {\n\t\ttarget[newParentID] = struct{}{}","sourceCodeStart":377,"sourceCodeEnd":413,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/dependency.go#L377-L413","documentation":"Wraps a failure of depRepo.ListByIssueIDs when reparent reads the child's current parent-child edges before computing the set replacement. Reparenting replaces the child's whole existing parent set (a child can carry multiple parent-child edges), so this read is the first durable step; when it fails, no edges have been changed yet and the error pinpoints the read as the failure point.","triggerScenarios":"Reparent or ReparentWisp calls ListByIssueIDs for the child filtered to DepParentChild / outgoing direction, and the repo query fails — DB connection lost, SQL error, Dolt lock/transaction conflict, or context cancellation during the scan.","commonSituations":"Database unavailable mid-command; context deadline exceeded for children with many edges; Dolt server error during concurrent sync; schema/version mismatch after an upgrade.","solutions":["Fix the wrapped repository error surfaced by %w (connectivity, SQL, context).","Retry the reparent — the read is side-effect-free, so re-invoking is safe.","Increase the context deadline if large edge sets are timing out.","Check database health and schema currency (bd doctor) before retrying batch reparents."],"exampleFix":"// before: assuming reparent failed during the write\nif err := uc.Reparent(ctx, child, parent, actor); err != nil {\n    rollbackParentWrite(child) // wrong: nothing was written\n}\n// after: the failure was the pre-write read; just retry\nif err := uc.Reparent(ctx, child, parent, actor); err != nil {\n    if isTransient(err) {\n        err = uc.Reparent(ctx, child, parent, actor)\n    }\n}","handlingStrategy":"retry","validationCode":"// Confirm storage is reachable before starting reparent batch\nif err := db.PingContext(ctx); err != nil {\n    return fmt.Errorf(\"storage unreachable: %w\", err)\n}\n// Optionally pre-fetch the child's current parents to fail fast\n_, err := uc.ListByWispIDs(ctx, []string{childID}, DepListFilter{})","typeGuard":null,"tryCatchPattern":"err := uc.Reparent(ctx, childID, newParentID, actor)\nfor attempt := 0; err != nil && attempt < 3; attempt++ {\n    if !errors.Is(err, context.DeadlineExceeded) && !isTransient(err) {\n        break\n    }\n    time.Sleep(backoff(attempt))\n    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n    defer cancel()\n    err = uc.Reparent(ctx, childID, newParentID, actor)\n}","preventionTips":["Use generous context deadlines; the pre-write read scans all parent-child edges","Retry safely: this failure occurs before any edge is modified","Check DB health/locks during concurrent syncs before reparent jobs","Pre-resolve current parents yourself to catch storage issues early"],"tags":["go","storage","reparent","dependency-graph","query"],"backgroundTag":"storage-query-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}