{"record":{"id":"73800b42cc71bb4a","repo":"gastownhall/beads","slug":"partition-wisp-ids-scan-w","errorCode":null,"errorMessage":"partition wisp ids: scan: %w","messagePattern":"partition wisp ids: scan: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/issueops/wisp_routing.go","lineNumber":170,"sourceCode":"\t\t\tplaceholders[i] = \"?\"\n\t\t\targs[i] = id\n\t\t}\n\t\t//nolint:gosec // G201: only ? placeholders in the IN clause.\n\t\trows, qErr := tx.QueryContext(ctx,\n\t\t\tfmt.Sprintf(\"SELECT id FROM wisps WHERE id IN (%s)\", strings.Join(placeholders, \",\")),\n\t\t\targs...)\n\t\tif qErr != nil {\n\t\t\t// Wisps table may not exist yet on older schemas — treat as \"no wisps\".\n\t\t\tif isTableNotExistError(qErr) {\n\t\t\t\treturn nil, append([]string(nil), ids...), nil\n\t\t\t}\n\t\t\treturn nil, nil, fmt.Errorf(\"partition wisp ids: %w\", qErr)\n\t\t}\n\t\tfor rows.Next() {\n\t\t\tvar id string\n\t\t\tif scanErr := rows.Scan(&id); scanErr != nil {\n\t\t\t\t_ = rows.Close()\n\t\t\t\treturn nil, nil, fmt.Errorf(\"partition wisp ids: scan: %w\", scanErr)\n\t\t\t}\n\t\t\twispSet[id] = struct{}{}\n\t\t}\n\t\t_ = rows.Close()\n\t\tif rowsErr := rows.Err(); rowsErr != nil {\n\t\t\treturn nil, nil, fmt.Errorf(\"partition wisp ids: rows: %w\", rowsErr)\n\t\t}\n\t}\n\n\tfor _, id := range ids {\n\t\tif _, ok := wispSet[id]; ok {\n\t\t\twispIDs = append(wispIDs, id)\n\t\t} else {\n\t\t\tpermIDs = append(permIDs, id)\n\t\t}\n\t}\n\treturn wispIDs, permIDs, nil\n}","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/issueops/wisp_routing.go#L152-L188","documentation":"During PartitionWispIDsInTx row iteration, rows.Scan into a single string ID failed. This wraps the sql.Rows.Scan error — typically a type/conversion mismatch or a driver-level row error surfaced at scan time. The partial wispSet is discarded so callers never see an untrustworthy partition.","triggerScenarios":"Calling PartitionWispIDsInTx (or its wrappers: GetCommentsForIssuesInTx, GetCommentCountsInTx, ResolveDeletionSetInTx, GetDependencyRecordsForIssuesInTx, GetBlockingInfoForIssuesInTx, GetLabelsForIssuesInTx) when a row in the wisps result set cannot be scanned into a string — e.g. the id column type changed, the connection broke mid-iteration, or the context was cancelled between Next() and Scan().","commonSituations":"Schema drift where wisps.id is no longer a plain string/varchar; connection killed mid-result-stream by a proxy or Dolt server restart; context cancellation racing row iteration.","solutions":["Check the wrapped scan error for sql.ErrNoRows vs conversion errors; verify the wisps.id column type matches expectations","Re-run migrations so the schema matches what this code expects (id as string)","Retry with a fresh connection/transaction if the cause was a dropped connection","Ensure the context is not cancelled prematurely by the caller (check parent deadlines)"],"exampleFix":"// before\nrows, err := tx.QueryContext(ctx, q, args...)\n// ... Scan fails with conversion error after schema drift\n// after\n// 1. run migrations: bd migrate (or equivalent) to restore wisps.id type\n// 2. retry the partition on a fresh tx\nwispIDs, permIDs, err := issueops.PartitionWispIDsInTx(freshCtx, freshTx, ids)","handlingStrategy":"try-catch","validationCode":"// ensure schema matches expectations before partitioning\n_ = store.RunMigrations(ctx) // keeps wisps.id column type as expected","typeGuard":null,"tryCatchPattern":"wispIDs, permIDs, err := issueops.PartitionWispIDsInTx(ctx, tx, ids)\nif err != nil {\n\tvar scanErr *fmt.ScanError // or inspect wrapped driver error\n\tif strings.Contains(err.Error(), \"scan\") {\n\t\t// schema drift suspected: run migrations, then retry\n\t}\n\treturn err\n}","preventionTips":["Pin schema version and run migrations before app start","Avoid altering wisps.id column type without a migration plan","Ensure parent contexts outlive the row iteration","Use stable connections (no aggressive proxy idle kills) for long result streams"],"tags":["database","wisp-routing","row-scan"],"backgroundTag":"db-row-scan-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}