gastownhall/beads · error

scan is_blocked from %s: %w

Error message

scan is_blocked from %s: %w

What it means

This error wraps a *sql.Rows.Scan failure while reading (issue_id, is_blocked) pairs in readIsBlockedIntoFromTable, part of IsBlockedBatchInTx. It fires when a row's column values cannot be converted into the declared Go destinations (a string id and an int blocked flag) — typically a column-count or type mismatch between the query and Scan targets. The library throws it so batch is_blocked computation fails loudly rather than silently producing wrong blocking state.

Source

Thrown at internal/storage/issueops/dependency_queries.go:1041

//nolint:gosec // G201: table is a hardcoded "issues" or "wisps"; placeholders are ? only.
func readIsBlockedIntoFromTable(ctx context.Context, tx DBTX, table string, ids []string, seen, blocked map[string]bool) error {
	for start := 0; start < len(ids); start += queryBatchSize {
		end := start + queryBatchSize
		if end > len(ids) {
			end = len(ids)
		}
		placeholders, args := buildSQLInClause(ids[start:end])
		rows, err := tx.QueryContext(ctx, fmt.Sprintf(
			"SELECT id, is_blocked FROM %s WHERE id IN (%s)", table, placeholders), args...)
		if err != nil {
			return fmt.Errorf("read is_blocked from %s: %w", table, err)
		}
		for rows.Next() {
			var id string
			var b int
			if err := rows.Scan(&id, &b); err != nil {
				_ = rows.Close()
				return fmt.Errorf("scan is_blocked from %s: %w", table, err)
			}
			// Keep the first-seen (issues) value and skip any later (wisps)
			// duplicate, so the batch is_blocked matches per-row IsBlocked.
			if seen[id] {
				continue
			}
			seen[id] = true
			blocked[id] = b != 0
		}
		_ = rows.Close()
		if err := rows.Err(); err != nil {
			return fmt.Errorf("is_blocked rows from %s: %w", table, err)
		}
	}
	return nil
}

// scanDependencyRow scans a single dependency row from a *sql.Rows.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Run schema migrations / `bd doctor` to align the database schema with the binary's expected tables and columns.
  2. Rebuild or re-sync the database from the canonical source (bd dolt pull / re-import) if schema drift is unfixable in place.
  3. Upgrade or downgrade the beads binary so its query matches the existing schema version.
  4. Check driver compatibility (dolthub driver version) if the type of the computed is_blocked column changed.

Example fix

// before: schema drifted, extra column returned by query
if err := rows.Scan(&id, &b); err != nil {
	return fmt.Errorf("scan is_blocked from %s: %w", table, err)
}
// after: make destinations match query shape, tolerate NULL
var bid sql.NullInt64
if err := rows.Scan(&id, &bid); err != nil {
	return fmt.Errorf("scan is_blocked from %s: %w", table, err)
}
b = int(bid.Int64)
Defensive patterns

Strategy: try-catch

Validate before calling

// verify schema before batch check
rows, err := db.Query("SELECT issue_id, is_blocked FROM issues LIMIT 1")
if err != nil { return err }
cols, _ := rows.Columns()
if len(cols) != 2 || cols[0] != "issue_id" || cols[1] != "is_blocked" {
	return fmt.Errorf("unexpected issues shape %v; run migrations", cols)
}
rows.Close()

Type guard

func isScanErr(err error) bool {
	var se *sql.StorageError
	return errors.As(err, &se) || strings.Contains(err.Error(), "sql: expected")
}

Try / catch

blocked, err := ops.IsBlockedBatchInTx(ctx, tx, ids)
if err != nil {
	if errors.Is(err, context.DeadlineExceeded) || isTransient(err) {
		return retryBatch(ctx, ids)
	}
	return fmt.Errorf("batch is_blocked unavailable (schema drift?): %w", err)
}

Prevention

When it happens

Trigger: Calling IsBlockedBatchInTx (directly or via higher-level batch blocking APIs) when the underlying issues/wisps table schema no longer matches the hardcoded SELECT column list — e.g. the query returns extra/missing columns or the is_blocked expression changes type (e.g. NULL, string instead of int).

Common situations: Running a beads binary against a Dolt database created by a different (older/newer) version whose dependency/is_blocked schema drifted; manual schema edits; a driver that returns a different integer type for the boolean expression.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/d44aef9f32b20dc7. Report an issue: GitHub.