gastownhall/beads · error

scan wisp dependent: %w

Error message

scan wisp dependent: %w

What it means

During the recursive wisp-dependents traversal, scanning a row's issue_id into a string failed. This wraps rows.Scan errors — the row shape didn't match the expected single string column. The row set is closed and traversal aborts, returning the partial discovered set.

Source

Thrown at internal/storage/issueops/bulk_ops.go:396

		if end > len(toProcess) {
			end = len(toProcess)
		}
		batch := toProcess[:end]
		toProcess = toProcess[end:]

		placeholders, args := buildSQLInClause(batch)
		rows, err := tx.QueryContext(ctx,
			fmt.Sprintf(`SELECT issue_id FROM wisp_dependencies WHERE %s IN (%s)`, DepTargetExpr, placeholders),
			args...)
		if err != nil {
			return discovered, fmt.Errorf("query wisp dependents: %w", err)
		}

		for rows.Next() {
			var depID string
			if err := rows.Scan(&depID); err != nil {
				_ = rows.Close()
				return discovered, fmt.Errorf("scan wisp dependent: %w", err)
			}
			if !seen[depID] {
				seen[depID] = true
				discovered[depID] = true
				toProcess = append(toProcess, depID)
			}
		}
		_ = rows.Close()
		if err := rows.Err(); err != nil {
			return discovered, fmt.Errorf("iterate wisp dependents: %w", err)
		}
	}

	return discovered, nil
}

// GetRepoMtimeInTx returns the cached mtime (nanoseconds) for a repo path.
// Returns 0 if no cache entry exists.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Run pending schema migrations so wisp_dependencies matches the expected shape.
  2. Check the wrapped error for NULL-vs-non-string mismatches and clean bad rows.
  3. Verify all replicas/nodes run the same beads version during upgrades.
  4. Retry after fixing the schema; the traversal is read-only so it is safe to re-run.

Example fix

// before
// schema drift: issue_id column is NULLable and contains NULLs -> scan fails
// after
// migrate: UPDATE wisp_dependencies SET issue_id='' WHERE issue_id IS NULL; ALTER TABLE wisp_dependencies MODIFY issue_id TEXT NOT NULL;
Defensive patterns

Strategy: validation

Validate before calling

// preflight: ensure the column is present and non-NULL
row := db.QueryRow("SELECT COUNT(*) FROM wisp_dependencies WHERE issue_id IS NULL")
var n int; _ = row.Scan(&n)
if n > 0 { return fmt.Errorf("%d rows with NULL issue_id; run migration first", n) }

Try / catch

_, err := store.FindWispDependentsRecursive(ctx, tx, rootID, max)
if err != nil && strings.Contains(err.Error(), "scan wisp dependent:") {
    return fmt.Errorf("wisp_dependencies schema mismatch; run migrations: %w", err)
}

Prevention

When it happens

Trigger: Calling FindWispDependentsRecursiveInTx when the wisp_dependencies row returned has a different column type/shape than expected — e.g. schema drift after an upgrade, or DepTargetExpr resolving to a non-string column.

Common situations: Running new code against an old (unmigrated) wisp_dependencies table; a driver returning NULL for issue_id into a non-nullable string scan; mixed-version replicas during a rolling upgrade.

Related errors


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