{"record":{"id":"555d0c6eafc33f71","repo":"gastownhall/beads","slug":"iterate-dependents-for-batch-from-s-w","errorCode":null,"errorMessage":"iterate dependents for batch from %s: %w","messagePattern":"iterate dependents for batch from (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/delete.go","lineNumber":451,"sourceCode":"\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\treturn nil, fmt.Errorf(\"query dependents for batch from %s: %w\", depTable, err)\n\t\t\t}\n\n\t\t\tfor rows.Next() {\n\t\t\t\tvar depID string\n\t\t\t\tif err := rows.Scan(&depID); err != nil {\n\t\t\t\t\t_ = rows.Close()\n\t\t\t\t\treturn nil, fmt.Errorf(\"scan dependent: %w\", err)\n\t\t\t\t}\n\t\t\t\tif !result[depID] {\n\t\t\t\t\tresult[depID] = true\n\t\t\t\t\ttoProcess = append(toProcess, depID)\n\t\t\t\t}\n\t\t\t}\n\t\t\t_ = rows.Close()\n\t\t\tif err := rows.Err(); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"iterate dependents for batch from %s: %w\", depTable, err)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\n//nolint:gosec // G201: table is selected by callers from fixed issue/wisp auxiliary tables.\nfunc CountRowsForIssueIDsInTx(ctx context.Context, tx DBTX, table string, ids []string) (int, error) {\n\ttotal := 0\n\tfor i := 0; i < len(ids); i += deleteBatchSize {\n\t\tend := i + deleteBatchSize\n\t\tif end > len(ids) {\n\t\t\tend = len(ids)\n\t\t}\n\t\tinClause, args := buildSQLInClause(ids[i:end])\n\t\tvar count int\n\t\tif err := tx.QueryRowContext(ctx,","sourceCodeStart":433,"sourceCodeEnd":469,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/delete.go#L433-L469","documentation":"After consuming all rows, FindAllDependentsInTx checks rows.Err(); a non-nil result (driver error during iteration, connection reset while streaming rows) is wrapped with this message naming the table. This catches failures that occur mid-iteration rather than at query time.","triggerScenarios":"Cascade delete traversal (ResolveDeletionSetInTx → FindAllDependentsInTx) where the result set stream from dependencies or wisp_dependencies breaks mid-read — network drop to remote Dolt, driver timeout, or context cancellation during iteration.","commonSituations":"Large dependent sets streamed from a remote database over a flaky connection; context deadline expiring while iterating many batches.","solutions":["Retry the cascade delete with a stable connection to the database","Increase the context timeout so long traversals are not cancelled mid-iteration","For remote Dolt, check network stability / server logs for the mid-query failure"],"exampleFix":"// before\nctx := context.Background()\n// after\nctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)\ndefer cancel()\n// pass ctx so large traversals aren't cut off mid-rows","handlingStrategy":"retry","validationCode":"// confirm DB reachable with adequate headroom before traversal\nctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)\ndefer cancel()\nif err := db.PingContext(ctx); err != nil { return err }","typeGuard":null,"tryCatchPattern":"_, err := DeleteIssuesInTx(ctx, ctxArg, ids, WithCascade())\nif err != nil && strings.Contains(err.Error(), \"iterate dependents for batch from\") {\n\t// transient stream failure; retry after connection check\n\treturn retryWithBackoff(ctx, ids)\n}\nreturn err","preventionTips":["Use long-lived, stable connections for remote Dolt cascades","Set context timeouts that cover the full traversal","Avoid cancelling the parent context mid-delete","Retry transient network failures with backoff"],"tags":["database","rows-iteration","network","cascade"],"backgroundTag":"rows-iteration-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}