gastownhall/beads · error

scan blocker edge: %w

Error message

scan blocker edge: %w

What it means

Wraps a rows.Scan failure while reading (dependsOnID, depType) edge rows in IsBlockedInTx. The edge query succeeded but a returned row could not be scanned into two strings, indicating column type/shape mismatch or NULLs in the dep table.

Source

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

		dependsOnID, depType string
	}
	var edges []depEdge
	for _, depTable := range []string{"dependencies", "wisp_dependencies"} {
		rows, err := tx.QueryContext(ctx, fmt.Sprintf(`
			SELECT %s AS depends_on_id, type FROM %s
			WHERE issue_id = ? AND type IN ('blocks', 'waits-for', 'conditional-blocks')
		`, DepTargetExpr, depTable), issueID)
		if err != nil {
			if optionalBlockedTable(depTable) && isTableNotExistError(err) {
				continue
			}
			return false, nil, fmt.Errorf("check blockers from %s: %w", depTable, err)
		}
		for rows.Next() {
			var e depEdge
			if err := rows.Scan(&e.dependsOnID, &e.depType); err != nil {
				_ = rows.Close()
				return false, nil, fmt.Errorf("scan blocker edge: %w", err)
			}
			edges = append(edges, e)
		}
		_ = rows.Close()
		if err := rows.Err(); err != nil {
			return false, nil, fmt.Errorf("blocker edge rows from %s: %w", depTable, err)
		}
	}

	if len(edges) == 0 {
		return true, nil, nil
	}

	blockerIDs := make([]string, 0, len(edges))
	for _, e := range edges {
		blockerIDs = append(blockerIDs, e.dependsOnID)
	}
	statusByID, err := loadStatusByIDInTx(ctx, tx, blockerIDs)

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped scan error to find the offending column
  2. Ensure depends_on/id and type columns are TEXT NOT NULL per schema
  3. Recreate or migrate the dependency table to canonical schema
  4. Re-run the blocked check after repair

Example fix

// before
SELECT depends_on_id, type FROM deps WHERE ...
// after (defensive)
SELECT COALESCE(depends_on_id,''), COALESCE(type,'') FROM deps WHERE ...
Defensive patterns

Strategy: try-catch

Validate before calling

// check column types
rows, _ := db.Query("SELECT typeof(depends_on_id), typeof(type) FROM deps LIMIT 1")

Try / catch

if err := issueops.IsBlocked(ctx, id); err != nil {
	if strings.Contains(err.Error(), "scan blocker edge") {
		// schema drift: fix column types to TEXT, then retry
	}
	return err
}

Prevention

When it happens

Trigger: Iterating blocker edges when a dependency table row has unexpected column types (non-TEXT id/type columns, NULL values) or the table schema differs from what the query expects.

Common situations: Hand-edited or externally written dep tables with wrong column types; upgrade-related schema drift; corrupted rows.

Related errors


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