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

  1. Locate and repair NULL/invalid issue_id rows in the named dependency table
  2. COALESCE or filter NULLs in the query upstream of the scan
  3. Check driver behavior for the id column type and upgrade if conversion is unsupported
  4. 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

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


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