gastownhall/beads · error
scan: %w
Error message
scan: %w
What it means
The low-level wrap inside scanStringsInto when rows.Scan fails to read a child ID into a string. It is chained under the "deferred parents: %s/%s" wrappers (3048) so the outer message still identifies which edge query failed. Scan into a single string fails mainly for NULL values or incompatible driver types.
Source
Thrown at internal/storage/domain/db/ready_work.go:129
// children with a nil error.
if missingOptionalWispTable(err) {
continue
}
return nil, fmt.Errorf("deferred parents: %s/%s: %w", e.depTable, e.issueTable, err)
}
if err := scanStringsInto(rows, &childIDs); err != nil {
return nil, fmt.Errorf("deferred parents: %s/%s: %w", e.depTable, e.issueTable, err)
}
}
return childIDs, nil
}
func scanStringsInto(rows *sql.Rows, out *[]string) error {
defer func() { _ = rows.Close() }()
for rows.Next() {
var s string
if err := rows.Scan(&s); err != nil {
return fmt.Errorf("scan: %w", err)
}
*out = append(*out, s)
}
return rows.Err()
}
//nolint:gosec // G201: depTable is hardcoded.
func (r *issueSQLRepositoryImpl) getDescendantIDs(ctx context.Context, rootID string, maxDepth int) ([]string, error) {
if rootID == "" {
return nil, nil
}
queryDescendants := func(includeWisps bool) ([]string, bool, error) {
edgeQuery := fmt.Sprintf(`
SELECT issue_id, %s FROM dependencies WHERE type = 'parent-child'
`, depTargetExpr)
if includeWisps {
edgeQuery += fmt.Sprintf(`View on GitHub (pinned to 71377f2769)
Solutions
- Locate and repair NULL/invalid issue_id rows in the named dependency table
- COALESCE or filter NULLs in the query upstream of the scan
- Check driver behavior for the id column type and upgrade if conversion is unsupported
- If transient, retry; if structural, fix the data
Example fix
// before: SELECT dep.issue_id FROM ... (NULLs abort the scan) // after: SELECT COALESCE(dep.issue_id, '') FROM ... or add WHERE dep.issue_id IS NOT NULL
Defensive patterns
Strategy: validation
Validate before calling
// guard the data the scan will read
var bad int
_ = db.QueryRow("SELECT COUNT(*) FROM wisp_dependencies WHERE issue_id IS NULL").Scan(&bad)
if bad > 0 { return fmt.Errorf("%d rows with NULL issue_id will break scans", bad) } Type guard
func isStringScanError(err error) bool {
return err != nil && strings.HasPrefix(err.Error(), "scan: ")
} Try / catch
ready, err := repo.GetReadyWork(ctx, filter)
if err != nil && strings.Contains(err.Error(), "scan: ") {
// NULL/invalid id column: clean the data or COALESCE upstream, then retry
} Prevention
- Add NOT NULL constraints to id columns scanned as strings
- Use COALESCE/IS NOT NULL filters in queries feeding string scans
- Audit for orphaned dependency rows after deletions
- Upgrade drivers if type conversion is the culprit
When it happens
Trigger: A deferred-parent edge query yields a row whose single column is NULL or a type the driver refuses to convert to string.
Common situations: Dependency rows referencing nothing (NULL issue_id); driver returning []byte/other types it declines to convert; result-set corruption.
Related errors
- ErrScan
- failed to scan dependency keys: %w
- %s: %w
- failed to scan federation peer: %w
- failed to scan comment: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/babc8a23e3ff8288.
Report an issue: GitHub.