gastownhall/beads · error
count open wisp children for %s: %w
Error message
count open wisp children for %s: %w
What it means
Beads throws this when the wisp-side child count query — counting open parent-child dependencies in the optional wisp_dependencies table joined with wisps — fails with a driver error other than 'table does not exist'. If the wisp tables simply don't exist (optional feature) the count gracefully degrades to the durable count; any other SQL failure is wrapped with the parent id and aborts the close-policy check.
Source
Thrown at internal/storage/issueops/close.go:228
var wispCount int
//nolint:gosec // G201: targetColumn is validated above against two hardcoded identifiers.
wispQuery := fmt.Sprintf(`
SELECT COUNT(DISTINCT dependency.issue_id)
FROM wisp_dependencies AS dependency
JOIN wisps AS child ON child.id = dependency.issue_id
WHERE dependency.%s = ?
AND dependency.type = 'parent-child'
AND child.status != 'closed'
AND NOT EXISTS (
SELECT 1 FROM dependencies AS durable WHERE durable.id = dependency.id
)
`, targetColumn)
if err := tx.QueryRowContext(ctx, wispQuery, id).Scan(&wispCount); err != nil {
if optionalBlockedTable("wisp_dependencies") && isTableNotExistError(err) {
return durableCount, nil
}
return 0, fmt.Errorf("count open wisp children for %s: %w", id, err)
}
return durableCount + wispCount, nil
}
func createCloseCheckedSavepoint(ctx context.Context, tx DBTX) (string, error) {
name := closeCheckedSavepointPrefix + strconv.FormatUint(closeCheckedSavepointCounter.Add(1), 10)
//nolint:gosec // G201: name is a fixed identifier-safe prefix plus an atomic decimal counter.
if _, err := tx.ExecContext(ctx, "SAVEPOINT "+name); err != nil {
return "", fmt.Errorf("create checked close savepoint: %w", err)
}
return name, nil
}
func rollbackAndReleaseCloseCheckedSavepoint(ctx context.Context, tx DBTX, name string) error {
rollbackErr := rollbackToCloseCheckedSavepoint(ctx, tx, name)
releaseErr := releaseCloseCheckedSavepoint(ctx, tx, name)
return errors.Join(rollbackErr, releaseErr)
}View on GitHub (pinned to 71377f2769)
Solutions
- Read the wrapped driver error after 'count open wisp children for <id>:' for the true cause
- Retry the close if transient (connection, lock timeout)
- Run schema verification/migrations — wisp tables and columns must both exist and match the expected shape
- If wisps are unused in your deployment, ensure the optional-table gating (sqlbuild.OptionalWispTable) is configured so missing tables are skipped rather than half-present
Defensive patterns
Strategy: try-catch
Validate before calling
// Verify wisp schema is complete if you use wisps
var one int
if err := db.QueryRow("SELECT 1 FROM wisp_dependencies LIMIT 1").Scan(&one); err != nil && !dberrors.IsTableNotExist(err) {
return fmt.Errorf("wisp_dependencies unreadable: %w", err)
}
if err := db.QueryRow("SELECT 1 FROM wisps LIMIT 1").Scan(&one); err != nil && !dberrors.IsTableNotExist(err) {
return fmt.Errorf("wisps unreadable: %w", err)
} Try / catch
count, err := countOpenChildrenForTargetInTx(ctx, tx, id, col)
if err != nil {
if dberrors.IsTableNotExist(err) {
log.Printf("wisp schema missing for %s; proceeding with durable count only", id)
} else {
return fmt.Errorf("close of %s blocked: %w", id, err)
}
} Prevention
- Run migrations atomically so wisp_dependencies and wisps are created together or not at all
- If you don't use wisps, leave the optional-table gating enabled rather than creating partial tables
- Grant the DB user SELECT on both durable and wisp tables
- Retry closes after transient connection or lock-timeout errors
When it happens
Trigger: Calling CloseIssue / EnforceClosePolicyInTx when wisp_dependencies exists but the query fails: connection error, lock timeout, context cancellation, permission denied, or a malformed/corrupt wisp_dependencies table (e.g. missing the NOT EXISTS-referenced dependencies.id column).
Common situations: Database became unavailable between the durable and wisp queries; wisp schema partially created (wisp_dependencies exists but wisps missing or vice versa); migration created wisp tables without needed columns; lock contention on wisp_dependencies.
Understand the failure class
Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.
Related errors
- classify child-count target in wisps: %w
- search issues with counts: ephemeral wisp probe: %w
- search issues with counts: wisp probe: %w
- failed to begin transaction: %w
- failed to recompute is_blocked: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/c3e85e5b1f5dcbaa.
Report an issue: GitHub.