gastownhall/beads · error
get dependents: scan: %w
Error message
get dependents: scan: %w
What it means
This wraps rows.Scan failing while reading a dependent row (issue_id, type) in GetDependentsWithMetadataInTx. Scan errors mean the column values couldn't be decoded into the destination strings — usually a NULL in a NOT NULL-assumed column or an unexpected type.
Source
Thrown at internal/storage/issueops/dependencies.go:1179
//nolint:gosec // G201: table names come from WispTableRouting (hardcoded constants)
func GetDependentsWithMetadataInTx(ctx context.Context, tx DBTX, issueID string) ([]*types.IssueWithDependencyMetadata, error) {
type depMeta struct {
depID, depType string
}
// Query both dependency tables to find all dependents.
var deps []depMeta
for _, depTable := range []string{"dependencies", "wisp_dependencies"} {
rows, err := tx.QueryContext(ctx, fmt.Sprintf(
`SELECT issue_id, type FROM %s WHERE %s = ?`, depTable, DepTargetExpr), issueID)
if err != nil {
return nil, fmt.Errorf("get dependents from %s: %w", depTable, err)
}
for rows.Next() {
var d depMeta
if scanErr := rows.Scan(&d.depID, &d.depType); scanErr != nil {
_ = rows.Close()
return nil, fmt.Errorf("get dependents: scan: %w", scanErr)
}
deps = append(deps, d)
}
_ = rows.Close()
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("get dependents: rows from %s: %w", depTable, err)
}
}
if len(deps) == 0 {
return nil, nil
}
// Fetch all dependent issues.
ids := make([]string, len(deps))
for i, d := range deps {
ids[i] = d.depID
}View on GitHub (pinned to 71377f2769)
Solutions
- Find the offending row: SELECT issue_id, type FROM <named table> WHERE issue_id IS NULL OR type IS NULL.
- Backfill/repair NULL or malformed rows, then add a NOT NULL constraint if appropriate.
- If NULLs are expected, they must be fixed at the data layer — this code scans into plain string and cannot accept them.
- Confirm the driver/schema column types match (VARCHAR/TEXT, not BLOB or int).
Example fix
// before: nullable columns break Scan(&d.depID, &d.depType) // after: clean data and enforce NOT NULL UPDATE dependencies SET type = 'blocks' WHERE type IS NULL; ALTER TABLE dependencies MODIFY type VARCHAR(32) NOT NULL;
Defensive patterns
Strategy: validation
Validate before calling
// Detect rows that would fail Scan before calling the API
rows, err := tx.QueryContext(ctx, `SELECT issue_id, type FROM dependencies WHERE issue_id IS NULL OR type IS NULL
UNION ALL SELECT issue_id, type FROM wisp_dependencies WHERE issue_id IS NULL OR type IS NULL`)
if err != nil { return err }
defer rows.Close()
if rows.Next() { return errors.New("NULL dependency rows found; clean data before querying") } Type guard
func isScanErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "scan")
} Try / catch
deps, err := GetDependentsWithMetadataInTx(ctx, tx, issueID)
if err != nil {
if strings.Contains(err.Error(), "scan") {
return fmt.Errorf("data integrity problem in dependency table: %w", err)
}
return err
} Prevention
- Enforce NOT NULL on issue_id and type columns in both dependency tables.
- Validate data after imports or external writes.
- Add DB-level checks to reject NULL dependency rows at insert time.
- Audit for schema drift after upgrades.
When it happens
Trigger: GetDependentsWithMetadataInTx iterates rows from dependencies/wisp_dependencies and a row has NULL issue_id or type, or a column type the driver cannot convert to string.
Common situations: Rows inserted by older versions or external tools with NULL type values; schema altered to nullable after the code assumed NOT NULL; using a driver with strict type conversion (e.g. refusing to scan non-string types into string).
Related errors
- failed to begin transaction: %w
- failed to recompute is_blocked: %w
- failed to commit is_blocked repairs: %w
- failed to query orphaned dependencies: %w
- row iteration error: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/605dda39453349e1.
Report an issue: GitHub.