{"record":{"id":"1740f38648aa927f","repo":"gastownhall/beads","slug":"iterate-wisp-dependents-w","errorCode":null,"errorMessage":"iterate wisp dependents: %w","messagePattern":"iterate wisp dependents: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/bulk_ops.go","lineNumber":406,"sourceCode":"\t\tif err != nil {\n\t\t\treturn discovered, fmt.Errorf(\"query wisp dependents: %w\", err)\n\t\t}\n\n\t\tfor rows.Next() {\n\t\t\tvar depID string\n\t\t\tif err := rows.Scan(&depID); err != nil {\n\t\t\t\t_ = rows.Close()\n\t\t\t\treturn discovered, fmt.Errorf(\"scan wisp dependent: %w\", err)\n\t\t\t}\n\t\t\tif !seen[depID] {\n\t\t\t\tseen[depID] = true\n\t\t\t\tdiscovered[depID] = true\n\t\t\t\ttoProcess = append(toProcess, depID)\n\t\t\t}\n\t\t}\n\t\t_ = rows.Close()\n\t\tif err := rows.Err(); err != nil {\n\t\t\treturn discovered, fmt.Errorf(\"iterate wisp dependents: %w\", err)\n\t\t}\n\t}\n\n\treturn discovered, nil\n}\n\n// GetRepoMtimeInTx returns the cached mtime (nanoseconds) for a repo path.\n// Returns 0 if no cache entry exists.\nfunc GetRepoMtimeInTx(ctx context.Context, tx *sql.Tx, repoPath string) (int64, error) {\n\tvar mtimeNs int64\n\terr := tx.QueryRowContext(ctx,\n\t\t`SELECT mtime_ns FROM repo_mtimes WHERE repo_path = ?`, repoPath).Scan(&mtimeNs)\n\tif err != nil {\n\t\treturn 0, nil\n\t}\n\treturn mtimeNs, nil\n}\n","sourceCodeStart":388,"sourceCodeEnd":424,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/bulk_ops.go#L388-L424","documentation":"After consuming a batch of dependent rows, the traversal checks rows.Err() and wraps any iteration-level error with this message. This catches errors that surface lazily during row iteration (e.g. connection reset mid-result-set) rather than at query time. The traversal aborts and returns the partial discovered set.","triggerScenarios":"Calling FindWispDependentsRecursiveInTx when the result set is interrupted while streaming — network drop, server-side timeout/kill of the query, or context cancellation while rows are being read.","commonSituations":"Long-running traversal over a large dependency graph exceeding a query timeout; context deadline exceeded because the caller set a short timeout; network blip to a remote Dolt server.","solutions":["Increase the caller's context timeout for large cascades and retry.","Retry the traversal; it is read-only and the partial discovered set can be reused.","Reduce maxResults/batch size so each query completes within the timeout.","Check network stability to the storage server if errors recur."],"exampleFix":"// before\nctx := context.Background()\nids, _ := store.FindWispDependentsRecursive(ctx, tx, rootID, max) // killed at server query timeout\n// after\nctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)\ndefer cancel()\nids, err := store.FindWispDependentsRecursive(ctx, tx, rootID, max)","handlingStrategy":"retry","validationCode":"if deadline, ok := ctx.Deadline(); ok && time.Until(deadline) < 30*time.Second {\n    return errors.New(\"context deadline too short for dependency traversal; extend timeout\")\n}","typeGuard":null,"tryCatchPattern":"ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)\ndefer cancel()\nids, err := store.FindWispDependentsRecursive(ctx, tx, rootID, max)\nif err != nil && strings.Contains(err.Error(), \"iterate wisp dependents:\") {\n    // streaming error: retry with longer timeout\n    return retryWithLongerDeadline(ctx, tx, rootID, max)\n}","preventionTips":["Give traversal calls generous context timeouts.","Bound cascade size (maxResults) so result sets stream quickly.","Retry read-only traversals freely; they cannot corrupt state."],"tags":["database","iteration","timeout","wisp"],"backgroundTag":"result-set-iteration-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}