gastownhall/beads · error
scan remaining blocker: %w
Error message
scan remaining blocker: %w
What it means
Wraps a rows.Scan failure while reading (candidateID, blockerID) string pairs from a dependency-table result set in GetNewlyUnblockedByCloseInTx. This means the query returned rows whose columns could not be scanned into two strings — almost always a schema/type mismatch. The row iteration is aborted and the whole unblock computation fails.
Source
Thrown at internal/storage/issueops/dependency_queries.go:856
remainingByCandidate := make(map[string][]string, len(batch))
remainingBlockerSet := make(map[string]struct{})
for _, depTable := range []string{"dependencies", "wisp_dependencies"} {
//nolint:gosec // G201: depTable is hardcoded.
depRows, err := tx.QueryContext(ctx, fmt.Sprintf(`
SELECT issue_id, %s AS depends_on_id FROM %s
WHERE issue_id IN (%s) AND type = 'blocks' AND %s != ?
`, DepTargetExpr, depTable, placeholders, DepTargetExpr), append(batchArgs, closedIssueID)...)
if err != nil {
if optionalBlockedTable(depTable) && isTableNotExistError(err) {
continue
}
return nil, fmt.Errorf("check remaining blockers from %s: %w", depTable, err)
}
for depRows.Next() {
var candidateID, blockerID string
if err := depRows.Scan(&candidateID, &blockerID); err != nil {
_ = depRows.Close()
return nil, fmt.Errorf("scan remaining blocker: %w", err)
}
remainingByCandidate[candidateID] = append(remainingByCandidate[candidateID], blockerID)
remainingBlockerSet[blockerID] = struct{}{}
}
_ = depRows.Close()
if err := depRows.Err(); err != nil {
return nil, fmt.Errorf("remaining blocker rows from %s: %w", depTable, err)
}
}
remainingBlockerIDs := make([]string, 0, len(remainingBlockerSet))
for blockerID := range remainingBlockerSet {
remainingBlockerIDs = append(remainingBlockerIDs, blockerID)
}
sort.Strings(remainingBlockerIDs)
statusByID, err := loadStatusByIDInTx(ctx, tx, remainingBlockerIDs)
if err != nil {
return nil, fmt.Errorf("check remaining blocker status: %w", err)View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped driver error for the column/type that failed to scan
- Verify the dependency table columns are TEXT and NOT NULL as the schema expects
- Repair or recreate the table from the canonical schema/migrations
- Re-run the close/unblock operation after fixing the schema
Example fix
// before var candidateID, blockerID string rows.Scan(&candidateID, &blockerID) // panics/error on NULL // after (schema side: ensure columns are TEXT NOT NULL) SELECT COALESCE(issue_id,''), COALESCE(target_id,'') FROM ...
Defensive patterns
Strategy: try-catch
Validate before calling
// verify dep table column types are TEXT
rows, _ := db.Query("PRAGMA table_info(deps)")
// or run bd doctor schema checks before batch operations Try / catch
if err := tx.CloseIssue(ctx, id); err != nil {
if strings.Contains(err.Error(), "scan remaining blocker") {
// schema drift: run migrations / repair before retry
}
return err
} Prevention
- Never hand-edit or externally write dependency tables
- Run migrations on upgrade before using the CLI
- Validate schema with bd doctor after restores or syncs
When it happens
Trigger: The dependency table's columns have unexpected types (e.g. integer IDs instead of text, NULL values in non-nullable-expected columns, or extra/reordered columns) when GetNewlyUnblockedByCloseInTx iterates remaining blockers.
Common situations: Schema drift after an upgrade or hand-edited database; a custom dep table variant whose ID columns are not TEXT; corrupted rows containing NULLs.
Related errors
- scan blocker edge: %w
- failed to scan dolt_status: %w
- scan dirty config key: %w
- db: ChildCounterSQLRepository.NextChildID: scan: %w
- db: CommentSQLRepository.CountsByIssueIDs: scan: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/349d4eefae09fad7.
Report an issue: GitHub.