{"record":{"id":"0c25c7433b053795","repo":"gastownhall/beads","slug":"get-dependent-records-rows-w","errorCode":null,"errorMessage":"get dependent records: rows: %w","messagePattern":"get dependent records: rows: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependency_queries.go","lineNumber":213,"sourceCode":"\t\t\treturn fmt.Errorf(\"get dependent records from %s: %w\", depTable, err)\n\t\t}\n\t\tfor rows.Next() {\n\t\t\tdep, scanErr := scanDependentRow(rows)\n\t\t\tif scanErr != nil {\n\t\t\t\t_ = rows.Close()\n\t\t\t\treturn fmt.Errorf(\"get dependent records: scan: %w\", scanErr)\n\t\t\t}\n\t\t\t// De-dup by row id (depid): the wisp copy of a promoted edge carries\n\t\t\t// the same id as the durable copy scanned first, so skip the repeat.\n\t\t\tif seen[dep.ID] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tseen[dep.ID] = true\n\t\t\tresult[dep.DependsOnID] = append(result[dep.DependsOnID], dep)\n\t\t}\n\t\t_ = rows.Close()\n\t\tif err := rows.Err(); err != nil {\n\t\t\treturn fmt.Errorf(\"get dependent records: rows: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// Target-keyed dependents-read bounds. A raw read has no consumer to apply a\n// page size, so it clamps its own (default when limit <= 0, hard cap otherwise).\nconst (\n\tdefaultDependentRecordsLimit = 100\n\tmaxDependentRecordsLimit     = 500\n)\n\n// GetDependentRecordsInTx returns raw dependency rows whose TARGET is targetID\n// — the edges pointing AT targetID — from both the permanent and wisp\n// dependency tables. Unlike GetDependents/GetDependentsWithMetadata it does\n// NOT join or hydrate the source issues, so edges from dangling, cross-project,\n// or wisp sources are returned as raw rows rather than dropped. RAW READ: it\n// spans BOTH the `dependencies` and `wisp_dependencies` tables and applies no","sourceCodeStart":195,"sourceCodeEnd":231,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependency_queries.go#L195-L231","documentation":"This error wraps rows.Err() after iterating dependent dependency rows — a deferred/iterative error that occurred while fetching result rows (e.g. the connection died mid-iteration). It is distinct from the initial query error and from per-row scan errors.","triggerScenarios":"Calling GetDependentRecordsForIssuesInTx when the database connection drops or the context is canceled while streaming rows between rows.Next() calls, or a driver-level protocol error occurs during row retrieval.","commonSituations":"Long-running batched reads over many target IDs hitting a connection idle timeout; network flakiness to a remote Dolt/MySQL server; server-side kill of a long query.","solutions":["Check the wrapped driver error — it is almost always connection- or context-related and often transient.","Retry the read; these errors rarely indicate data problems.","Reduce batch work per transaction or raise connection idle/read timeouts.","Enable keepalives or connection health checks on the driver DSN.","Ensure the context passed in has adequate deadline for large result sets."],"exampleFix":"// before: single-shot read with a short deadline\nctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\ndefer cancel()\ndeps, err := GetDependentRecordsForIssuesInTx(ctx, tx, ids)\n// after: deadline sized to the workload, retry on transient rows errors\nctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\ndefer cancel()\ndeps, err := GetDependentRecordsForIssuesInTx(ctx, tx, ids)\nif err != nil && isTransientNetError(err) { deps, err = GetDependentRecordsForIssuesInTx(ctx, tx, ids) }","handlingStrategy":"retry","validationCode":"// Pre-flight: confirm the connection is alive before long batch reads\nif err := tx.QueryRowContext(ctx, \"SELECT 1\").Scan(&one); err != nil {\n    return fmt.Errorf(\"connection unhealthy before dependent-records read: %w\", err)\n}","typeGuard":"func isTransientRowError(err error) bool {\n    if err == nil { return false }\n    msg := err.Error()\n    return strings.Contains(msg, \"connection\") || strings.Contains(msg, \"bad connection\") || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded)\n}","tryCatchPattern":"err := getDependentRecordsIntoFromTable(ctx, tx, table, ids, seen, result)\nif err != nil && strings.Contains(err.Error(), \"rows: \") && isTransientRowError(err) {\n    err = retryWithBackoff(3, func() error { return getDependentRecordsIntoFromTable(ctx, tx, table, ids, seen, result) })\n}","preventionTips":["Raise connection idle/read timeouts for batch workloads","Size the context deadline to the number of batches","Enable TCP keepalives on the database DSN","Avoid holding transactions open across slow application logic"],"tags":["database","connection","rows-iteration","storage"],"backgroundTag":"connection-lost-mid-query","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}