gastownhall/beads · error

count dependent records from %s: %w

Error message

count dependent records from %s: %w

What it means

This error wraps the COUNT(*) QueryRowContext failure in countDependentRecordsFromTable, which counts inbound dependency rows for a target in a single table ('dependencies' or 'wisp_dependencies'), optionally filtered by dependency type. It means the count query failed to execute or its result could not be scanned into an int.

Source

Thrown at internal/storage/issueops/dependency_queries.go:425

		wispWhere, durableWhere)
	var n int
	if err := tx.QueryRowContext(ctx, query, args...).Scan(&n); err != nil {
		return 0, fmt.Errorf("count wisp-only dependent records: %w", err)
	}
	return n, nil
}

//nolint:gosec // G201: depTable is a hardcoded constant; targetID/depType are bound as parameters.
func countDependentRecordsFromTable(ctx context.Context, tx DBTX, depTable, targetID, depType string) (int, error) {
	query := fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE %s", depTable, depTargetEqualsOr())
	args := []any{targetID, targetID, targetID}
	if depType != "" {
		query += " AND type = ?"
		args = append(args, depType)
	}
	var n int
	if err := tx.QueryRowContext(ctx, query, args...).Scan(&n); err != nil {
		return 0, fmt.Errorf("count dependent records from %s: %w", depTable, err)
	}
	return n, nil
}

func GetDependencyCountsInTx(ctx context.Context, tx DBTX, issueIDs []string) (map[string]*types.DependencyCounts, error) {
	if len(issueIDs) == 0 {
		return make(map[string]*types.DependencyCounts), nil
	}

	result := make(map[string]*types.DependencyCounts)
	for _, id := range issueIDs {
		result[id] = &types.DependencyCounts{}
	}

	depTables := []string{"dependencies", "wisp_dependencies"}
	if empty, probeErr := wispsTableEmptyOrMissingInTx(ctx, tx); probeErr != nil {
		return nil, fmt.Errorf("get dependency counts: probe: %w", probeErr)
	} else if empty {

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped driver error to classify: missing table vs connection vs lock timeout.
  2. Run schema migrations so the dependency tables exist with the target indexes (idx_dep_*).
  3. Retry on transient errors; consider serializing heavy writers if lock contention is the cause.
  4. Confirm DB user read permissions on the dependency tables.
  5. If scanning fails, check that COUNT returns an integer type the driver maps to int.
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check table existence and connectivity
var n int
if err := tx.QueryRowContext(ctx, "SELECT COUNT(*) FROM dependencies LIMIT 1").Scan(&n); err != nil {
    return fmt.Errorf("dependencies table not countable: %w", err)
}

Type guard

func tableExists(ctx context.Context, tx DBTX, table string) bool {
    var one int
    err := tx.QueryRowContext(ctx, "SELECT 1 FROM "+table+" LIMIT 1").Scan(&one)
    return err == nil
}

Try / catch

n, err := CountDependentRecordsInTx(ctx, tx, targetID, depType)
if err != nil {
    switch {
    case isTableNotExistError(err): // migrate or return 0
    case isLockTimeout(err): // retry after backing off
    case isTransientNetError(err): // retry
    default: return err
    }
}

Prevention

When it happens

Trigger: Calling CountDependentRecordsInTx when the table is missing (pre-migration schema), the target predicate SQL fails on the driver, context cancellation, connection drop, or COUNT result scan failure.

Common situations: Older schema without the dependency table; remote database connection issues; driver incompatibility with the depTargetEqualsOr OR predicate; locked database during a long write from another process.

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


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