{"record":{"id":"babc8a23e3ff8288","repo":"gastownhall/beads","slug":"scan-w-babc8a","errorCode":null,"errorMessage":"scan: %w","messagePattern":"scan: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/db/ready_work.go","lineNumber":129,"sourceCode":"\t\t\t// children with a nil error.\n\t\t\tif missingOptionalWispTable(err) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"deferred parents: %s/%s: %w\", e.depTable, e.issueTable, err)\n\t\t}\n\t\tif err := scanStringsInto(rows, &childIDs); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"deferred parents: %s/%s: %w\", e.depTable, e.issueTable, err)\n\t\t}\n\t}\n\treturn childIDs, nil\n}\n\nfunc scanStringsInto(rows *sql.Rows, out *[]string) error {\n\tdefer func() { _ = rows.Close() }()\n\tfor rows.Next() {\n\t\tvar s string\n\t\tif err := rows.Scan(&s); err != nil {\n\t\t\treturn fmt.Errorf(\"scan: %w\", err)\n\t\t}\n\t\t*out = append(*out, s)\n\t}\n\treturn rows.Err()\n}\n\n//nolint:gosec // G201: depTable is hardcoded.\nfunc (r *issueSQLRepositoryImpl) getDescendantIDs(ctx context.Context, rootID string, maxDepth int) ([]string, error) {\n\tif rootID == \"\" {\n\t\treturn nil, nil\n\t}\n\n\tqueryDescendants := func(includeWisps bool) ([]string, bool, error) {\n\t\tedgeQuery := fmt.Sprintf(`\n\t\t\tSELECT issue_id, %s FROM dependencies WHERE type = 'parent-child'\n\t\t`, depTargetExpr)\n\t\tif includeWisps {\n\t\t\tedgeQuery += fmt.Sprintf(`","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/db/ready_work.go#L111-L147","documentation":"The low-level wrap inside scanStringsInto when rows.Scan fails to read a child ID into a string. It is chained under the \"deferred parents: %s/%s\" wrappers (3048) so the outer message still identifies which edge query failed. Scan into a single string fails mainly for NULL values or incompatible driver types.","triggerScenarios":"A deferred-parent edge query yields a row whose single column is NULL or a type the driver refuses to convert to string.","commonSituations":"Dependency rows referencing nothing (NULL issue_id); driver returning []byte/other types it declines to convert; result-set corruption.","solutions":["Locate and repair NULL/invalid issue_id rows in the named dependency table","COALESCE or filter NULLs in the query upstream of the scan","Check driver behavior for the id column type and upgrade if conversion is unsupported","If transient, retry; if structural, fix the data"],"exampleFix":"// before: SELECT dep.issue_id FROM ...  (NULLs abort the scan)\n// after: SELECT COALESCE(dep.issue_id, '') FROM ...  or add WHERE dep.issue_id IS NOT NULL","handlingStrategy":"validation","validationCode":"// guard the data the scan will read\nvar bad int\n_ = db.QueryRow(\"SELECT COUNT(*) FROM wisp_dependencies WHERE issue_id IS NULL\").Scan(&bad)\nif bad > 0 { return fmt.Errorf(\"%d rows with NULL issue_id will break scans\", bad) }","typeGuard":"func isStringScanError(err error) bool {\n\treturn err != nil && strings.HasPrefix(err.Error(), \"scan: \")\n}","tryCatchPattern":"ready, err := repo.GetReadyWork(ctx, filter)\nif err != nil && strings.Contains(err.Error(), \"scan: \") {\n\t// NULL/invalid id column: clean the data or COALESCE upstream, then retry\n}","preventionTips":["Add NOT NULL constraints to id columns scanned as strings","Use COALESCE/IS NOT NULL filters in queries feeding string scans","Audit for orphaned dependency rows after deletions","Upgrade drivers if type conversion is the culprit"],"tags":["database","scan","null","deferred"],"backgroundTag":"sql-row-scan-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}