{"record":{"id":"61ca8a58bfdff753","repo":"gastownhall/beads","slug":"iterating-dependencies-rows-for-migration-0053-id","errorCode":null,"errorMessage":"iterating dependencies rows for migration 0053 id backfill: %w","messagePattern":"iterating dependencies rows for migration 0053 id backfill: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/schema/migration_repairs.go","lineNumber":476,"sourceCode":"\t`)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"reading dependencies rows for migration 0053 id backfill: %w\", err)\n\t}\n\ttype edge struct {\n\t\tissueID                                              string\n\t\tdependsOnIssueID, dependsOnWispID, dependsOnExternal sql.NullString\n\t}\n\tvar edges []edge\n\tfor rows.Next() {\n\t\tvar e edge\n\t\tif err := rows.Scan(&e.issueID, &e.dependsOnIssueID, &e.dependsOnWispID, &e.dependsOnExternal); err != nil {\n\t\t\t_ = rows.Close()\n\t\t\treturn fmt.Errorf(\"scanning dependencies row for migration 0053 id backfill: %w\", err)\n\t\t}\n\t\tedges = append(edges, e)\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn fmt.Errorf(\"iterating dependencies rows for migration 0053 id backfill: %w\", err)\n\t}\n\t_ = rows.Close()\n\n\tfor _, e := range edges {\n\t\ttarget := firstNonNullString(e.dependsOnIssueID, e.dependsOnWispID, e.dependsOnExternal)\n\t\tif target == \"\" {\n\t\t\t// ck_dep_one_target (0041) should make a targetless row\n\t\t\t// unreachable; if one exists anyway, leave its id NULL here --\n\t\t\t// ensureDependenciesIDPrimaryKey below checks for exactly this\n\t\t\t// and fails loudly with an actionable count instead of letting a\n\t\t\t// blind MODIFY ... NOT NULL hard-fail on it, or silently keying\n\t\t\t// the table while pretending the row doesn't exist.\n\t\t\tcontinue\n\t\t}\n\t\tid := depid.New(e.issueID, target)\n\t\tif _, err := db.ExecContext(ctx, `\n\t\t\tUPDATE dependencies SET id = ?\n\t\t\tWHERE issue_id = ?","sourceCodeStart":458,"sourceCodeEnd":494,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/schema/migration_repairs.go#L458-L494","documentation":"This wraps a rows.Err() failure after iterating the backfill SELECT over dependencies rows with id IS NULL. rows.Err() reports a driver/network error encountered mid-iteration (e.g. connection dropped while streaming rows), distinct from a scan-type mismatch. All rows read so far are discarded and the repair aborts before updating any id.","triggerScenarios":"backfillDependenciesID collected some edges, then rows.Err() returns non-nil because the underlying connection failed or the server aborted the result set during iteration — idle timeouts, max_allowed_packet issues on wide result sets, or server shutdown mid-query.","commonSituations":"Long backfills over a flaky VPN/proxy connection; MySQL wait_timeout killing an idle-then-streaming connection; server memory/limit kills on very large dependencies tables.","solutions":["Re-run the repair — the WHERE id IS NULL filter makes backfill resumable and idempotent","Increase connection idle/wait timeouts in the DSN or server config","For large tables, chunk the backfill (LIMIT/OFFSET or by key) to shorten result-set lifetime","Stabilize the network path or run the repair locally against the clone"],"exampleFix":"// before: one giant streaming read over a flaky link\nrows := query(\"... WHERE id IS NULL\")\n// after: chunked, resumable backfill\nfor {\n    rows := query(\"... WHERE id IS NULL LIMIT 1000\")\n    if n := backfillChunk(rows); n == 0 { break }\n}","handlingStrategy":"retry","validationCode":"// ensure a stable connection before a potentially long streaming backfill\nif err := db.PingContext(ctx); err != nil {\n    return fmt.Errorf(\"repair aborted, unstable connection: %w\", err)\n}","typeGuard":null,"tryCatchPattern":"err := repairV53RigAndSplitTargets(ctx, db)\nif err != nil && strings.Contains(err.Error(), \"iterating dependencies rows\") {\n    // transient stream failure — backfill is resumable via WHERE id IS NULL\n    db = reconnect(db)\n    return repairV53RigAndSplitTargets(ctx, db)\n}","preventionTips":["Set generous read/write timeouts and keepalives in the DSN","Chunk large backfills to keep result sets short","Retry repairs on stream errors — id IS NULL filter makes them resumable","Run repairs close to the database (avoid flaky tunnels/VPNs)"],"tags":["database","backfill","mysql","connection-drop","schema-migration"],"backgroundTag":"connection-dropped-mid-query","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}