{"record":{"id":"e7987ddd99e67c5a","repo":"gastownhall/beads","slug":"scan-dependent-w-e7987d","errorCode":null,"errorMessage":"scan dependent: %w","messagePattern":"scan dependent: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/dependency_queries.go","lineNumber":341,"sourceCode":"\t\tif scanErr != nil {\n\t\t\treturn nil, fmt.Errorf(\"get dependent records: scan: %w\", scanErr)\n\t\t}\n\t\tdeps = append(deps, dep)\n\t}\n\treturn deps, rows.Err()\n}\n\n// scanDependentRow scans a dependents row that INCLUDES the row id (the keyset\n// cursor). The shared scanDependencyRow does not select id, and adding it there\n// would ripple through every source-keyed read, so the target-keyed read owns\n// this variant.\nfunc scanDependentRow(rows *sql.Rows) (*types.Dependency, error) {\n\tvar dep types.Dependency\n\tvar createdAt sql.NullTime\n\tvar metadata, threadID sql.NullString\n\n\tif err := rows.Scan(&dep.ID, &dep.IssueID, &dep.DependsOnID, &dep.Type, &createdAt, &dep.CreatedBy, &metadata, &threadID); err != nil {\n\t\treturn nil, fmt.Errorf(\"scan dependent: %w\", err)\n\t}\n\tif createdAt.Valid {\n\t\tdep.CreatedAt = createdAt.Time\n\t}\n\tif metadata.Valid {\n\t\tdep.Metadata = metadata.String\n\t}\n\tif threadID.Valid {\n\t\tdep.ThreadID = threadID.String\n\t}\n\treturn &dep, nil\n}\n\n// CountDependentRecordsInTx returns the number of DISTINCT inbound edges of\n// targetID, applying the same sargable target predicate and optional depType\n// filter as GetDependentRecordsInTx but no keyset/limit. Callers that want a\n// true total membership count need it without paging to exhaustion. Like the\n// paged read it is a RAW count spanning both tables; the caller applies any","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/dependency_queries.go#L323-L359","documentation":"This is the lowest-level decode error in dependency_queries.go: scanDependentRow failed to Scan a dependency row's eight columns into a types.Dependency struct (with sql.NullTime/NullString for nullable fields). All higher-level dependent-record and count errors funnel through it, so it always indicates a row-content or schema-shape problem, not a connection problem.","triggerScenarios":"Any read path (GetDependentRecordsForIssuesInTx, GetDependentRecordsInTx) encountering a row whose column count or types don't match: NULL issue_id/depends_on_id/type/id, non-time created_at, schema with missing or reordered columns.","commonSituations":"Partial migrations leaving tables with old column sets; manual data fixes that NULLed required columns; database restored from an incompatible version; corrupted storage files.","solutions":["Parse the wrapped error: it names the column/conversion that failed (e.g. 'converting NULL to string is unsupported').","Locate the bad row by id prefix of the failing table and repair or delete it.","Re-run or hand-apply pending migrations to match expected schema (id, issue_id, depends_on columns, type, created_at, created_by, metadata, thread_id).","If corruption is suspected, restore from backup or re-init the database.","Keep nullable columns in scans as sql.Null* when the schema permits NULL."],"exampleFix":"// before: schema missing thread_id column after partial migration\nSELECT id, issue_id, issue_id AS depends_on_id, type, created_at, created_by, metadata FROM dependencies;\n// after: complete migration adds the column\nALTER TABLE dependencies ADD COLUMN thread_id VARCHAR(64) NULL;","handlingStrategy":"validation","validationCode":"// Verify the table shape matches the scanner's expectation (8 columns, right names)\ncols, _ := tx.QueryContext(ctx, \"SELECT * FROM dependencies LIMIT 1\")\nts, _ := cols.ColumnTypes()\n// expect: id, issue_id, depends_on target column(s), type, created_at, created_by, metadata, thread_id\nif len(ts) < 8 { /* migration incomplete; repair before reading */ }","typeGuard":"func scanDependentSafe(rows *sql.Rows) (dep *types.Dependency, err error) {\n    defer func() { if r := recover(); r != nil { err = fmt.Errorf(\"scan dependent: %v\", r) } }()\n    return scanDependentRow(rows)\n}","tryCatchPattern":"dep, err := scanDependentRow(rows)\nif err != nil {\n    // unwrap to find failing column: errors.Is/As on the driver error, then repair schema/data\n    log.Printf(\"bad dependency row in %s: %v\", table, err)\n}","preventionTips":["Treat partial migrations as an alert condition; verify column set post-migration","Keep nullable columns nullable in scans (sql.NullString/NullTime)","Never alter dependency table schemas by hand","Run integration tests that round-trip a dependency edge through both tables"],"tags":["database","sql-scan","schema-migration","storage"],"backgroundTag":"sql-scan-type-mismatch","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}