{"record":{"id":"14df87f5a1586330","repo":"gastownhall/beads","slug":"scan-parent-child-dep-from-s-w","errorCode":null,"errorMessage":"scan parent-child dep from %s: %w","messagePattern":"scan parent-child dep from (.+?): %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/epic_closure.go","lineNumber":60,"sourceCode":"\tfor _, id := range epicIDs {\n\t\tepicSet[id] = true\n\t}\n\tfor _, depTable := range []string{\"dependencies\", \"wisp_dependencies\"} {\n\t\tdepRows, err := tx.QueryContext(ctx, fmt.Sprintf(`\n\t\t\tSELECT %s AS parent_id, issue_id FROM %s\n\t\t\tWHERE type = 'parent-child' AND %s IS NOT NULL\n\t\t`, DepTargetExpr, depTable, DepTargetExpr))\n\t\tif err != nil {\n\t\t\tif optionalBlockedTable(depTable) && isTableNotExistError(err) {\n\t\t\t\tcontinue // wisp_dependencies may not exist on pre-migration databases\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"failed to get parent-child deps from %s: %w\", depTable, err)\n\t\t}\n\t\tfor depRows.Next() {\n\t\t\tvar parentID, childID string\n\t\t\tif err := depRows.Scan(&parentID, &childID); err != nil {\n\t\t\t\tdepRows.Close()\n\t\t\t\treturn nil, fmt.Errorf(\"scan parent-child dep from %s: %w\", depTable, err)\n\t\t\t}\n\t\t\tif epicSet[parentID] {\n\t\t\t\tepicChildMap[parentID] = append(epicChildMap[parentID], childID)\n\t\t\t}\n\t\t}\n\t\tdepRows.Close()\n\t}\n\n\t// Step 3: Batch-fetch statuses for all child issues across all epics\n\tallChildIDs := make([]string, 0)\n\tfor _, children := range epicChildMap {\n\t\tallChildIDs = append(allChildIDs, children...)\n\t}\n\tchildStatusMap := make(map[string]string)\n\tif len(allChildIDs) > 0 {\n\t\t// Check both issues and wisps tables for child statuses (bd-w2w)\n\t\tfor _, table := range []string{\"issues\", \"wisps\"} {\n\t\t\tfor start := 0; start < len(allChildIDs); start += queryBatchSize {","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/epic_closure.go#L42-L78","documentation":"Each dependency row is scanned into two strings (parentID, childID). A Scan failure aborts with the table name included. This means a row in the parent-child dependency result does not have the expected two-string shape, so dependency graph construction stops.","triggerScenarios":"Calling GetEpicsEligibleForClosureInTx when a row from the parent-child dependency query yields NULL or non-convertible values for parent_id/child_id (via DepTargetExpr columns).","commonSituations":"Rows where the dep target expression evaluates to NULL for a 'parent-child' type; hand-edited or imported dependency rows with missing IDs; driver type-conversion quirks.","solutions":["Find offending rows: `SELECT * FROM <table> WHERE type='parent-child' AND (<target-col> IS NULL)` and fix or delete them.","Verify DepTargetExpr columns (issue_id/target) are populated for parent-child deps.","Re-import or repair the dependency data from a known-good export.","Update bd if the driver version changed column types."],"exampleFix":"// before\nDELETE FROM dependencies WHERE id = 'broken-row';\n// after\nUPDATE dependencies SET target_id = '<correct-id>' WHERE id = 'broken-row';","handlingStrategy":"validation","validationCode":"rows, _ := db.Query(\"SELECT issue_id, target FROM dependencies WHERE type='parent-child'\")\nfor rows.Next() {\n    var p, c sql.NullString\n    _ = rows.Scan(&p, &c)\n    if !p.Valid || !c.Valid { return fmt.Errorf(\"parent-child dep with NULL endpoint\") }\n}","typeGuard":"func isDepScanErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"scan parent-child dep from\")\n}","tryCatchPattern":"epics, err := GetEpicsEligibleForClosureInTx(ctx, tx)\nif err != nil {\n    if isDepScanErr(err) { return repairDependencyRowsThenRetry(ctx) }\n    return err\n}","preventionTips":["Prevent NULL dependency endpoints with DB-level NOT NULL constraints where possible.","Sanitize dependency data on import/export.","Don't modify dependencies rows outside bd.","Re-sync corrupted tables from the primary replica."],"tags":["go","sql","scan","data-integrity"],"backgroundTag":"sql-row-scan-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}