gastownhall/beads · error
get dependency records: scan: %w
Error message
get dependency records: scan: %w
What it means
During row iteration in getDependencyRecordsIntoFromTable, scanDependencyRow failed to decode a row, so the loop closes the rows and aborts with this wrapped scan error. It means one row's column count/types/NULLs didn't match the dependency struct expectations in the per-issue query path — the query succeeded but a result row was unreadable.
Source
Thrown at internal/storage/issueops/dependency_queries.go:126
batch := ids[start:end]
placeholders := make([]string, len(batch))
args := make([]any, len(batch))
for i, id := range batch {
placeholders[i] = "?"
args[i] = id
}
rows, err := tx.QueryContext(ctx, fmt.Sprintf(
`SELECT issue_id, %s AS depends_on_id, type, created_at, created_by, metadata, thread_id
FROM %s WHERE issue_id IN (%s) ORDER BY issue_id, depends_on_id, type, id`,
DepTargetExpr, depTable, strings.Join(placeholders, ",")), args...)
if err != nil {
return fmt.Errorf("get dependency records from %s: %w", depTable, err)
}
for rows.Next() {
dep, scanErr := scanDependencyRow(rows)
if scanErr != nil {
_ = rows.Close()
return fmt.Errorf("get dependency records: scan: %w", scanErr)
}
result[dep.IssueID] = append(result[dep.IssueID], dep)
}
_ = rows.Close()
if err := rows.Err(); err != nil {
return fmt.Errorf("get dependency records: rows: %w", err)
}
}
return nil
}
// GetDependentRecordsForIssuesInTx returns raw dependency rows keyed by TARGET
// id: for each id in targetIDs, the rows whose target is that id — its INCOMING
// edges, i.e. its dependents — spanning BOTH the durable and wisp dependency
// tables and applying NO type filter or visibility policy (the caller filters
// at hydration). It is the batched, target-keyed mirror of the source-keyed
// GetDependencyRecordsForIssuesInTx: one query per table per batch of
// queryBatchSize target ids, so cost is O(1 + N/queryBatchSize) round-trips perView on GitHub (pinned to 71377f2769)
Solutions
- Read the wrapped scan error to find the failing column, then inspect the offending row in the named table.
- Harden scanDependencyRow for the observed NULL/type shape (sql.NullString etc.) — the shared scanner is the single fix point for both query paths.
- Bring rows written by an older schema forward through the driver's migration path instead of patching rows ad hoc.
Defensive patterns
Strategy: try-catch
Try / catch
recs, err := GetDependencyRecordsForIssuesInTx(ctx, tx, ids)
if err != nil {
if strings.Contains(err.Error(), "get dependency records: scan") {
// row-shape issue in the per-issue path; inspect the failing row/column
}
return err
} Prevention
- Keep the shared scanDependencyRow NULL-tolerant for optional columns.
- Run mixed-version read tests after schema or driver changes.
- Avoid external writes to dependency tables that could produce unexpected row shapes.
When it happens
Trigger: scanDependencyRow erroring on a row fetched by GetDependencyRecordsForIssuesInTx or GetDependencyRecordsForIssuesFromTableInTx — unexpected NULL in created_by/metadata/thread_id, or a driver type mismatch on created_at for rows in either dependency table.
Common situations: Mixed-version rows (older writer schema vs current scanner); driver upgrade changing scan type behavior; rows written by an external tool directly into the dependency tables.
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/446b7cd5e1332c84.
Report an issue: GitHub.