gastownhall/beads · error

get dependents: rows from

Error message

get dependents: rows from %s: %w

What it means

This wraps rows.Err() failing after the dependent-row iteration completes in GetDependentsWithMetadataInTx. database/sql defers some driver errors (connection loss, context cancellation, protocol errors) to the end of iteration; this is where they surface. The message names the table whose row stream failed.

Solutions

  1. Unwrap the error to see the driver cause (context deadline, bad connection, etc.).
  2. Tune connection settings: ConnMaxLifetime below server wait_timeout, keepalives enabled.
  3. Ensure the caller's context has enough budget for the whole dependent traversal.
  4. Retry the read on a fresh transaction/connection if transient.

Example fix

// before: pool outlives server wait_timeout
sqlDB.SetConnMaxLifetime(0)
// after
sqlDB.SetConnMaxLifetime(4 * time.Minute)
sqlDB.SetConnMaxIdleTime(1 * time.Minute)
Defensive patterns

Strategy: retry

Validate before calling

// Confirm connectivity and budget before the call
if err := tx.PingContext(ctx); err != nil { return err }
_, ok := ctx.Deadline()
if !ok { ctx, cancel := context.WithTimeout(ctx, 30*time.Second); defer cancel(); _ = ok }

Type guard

func isTransientRowsErr(err error) bool {
    return errors.Is(err, context.DeadlineExceeded) || errors.Is(err, context.Canceled) || strings.Contains(err.Error(), "bad connection")
}

Try / catch

var deps []*types.IssueWithDependencyMetadata
err := retry.Do(3, func() error {
    var e error
    deps, e = GetDependentsWithMetadataInTx(ctx, tx, issueID)
    return e
})
if err != nil { return err }

Prevention

When it happens

Trigger: Calling GetDependentsWithMetadataInTx when the connection drops or ctx is cancelled while draining dependent rows from dependencies/wisp_dependencies.

Common situations: Idle connection reaped by the server mid-query; request deadline exceeded during a large dependent fan-out; network interruption on remote DB; proxy/load-balancer killing long-lived connections.

Related errors


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

Appendix: source

Thrown at internal/storage/issueops/dependencies.go:1185

	// Query both dependency tables to find all dependents.
	var deps []depMeta
	for _, depTable := range []string{"dependencies", "wisp_dependencies"} {
		rows, err := tx.QueryContext(ctx, fmt.Sprintf(
			`SELECT issue_id, type FROM %s WHERE %s = ?`, depTable, DepTargetExpr), issueID)
		if err != nil {
			return nil, fmt.Errorf("get dependents from %s: %w", depTable, err)
		}
		for rows.Next() {
			var d depMeta
			if scanErr := rows.Scan(&d.depID, &d.depType); scanErr != nil {
				_ = rows.Close()
				return nil, fmt.Errorf("get dependents: scan: %w", scanErr)
			}
			deps = append(deps, d)
		}
		_ = rows.Close()
		if err := rows.Err(); err != nil {
			return nil, fmt.Errorf("get dependents: rows from %s: %w", depTable, err)
		}
	}

	if len(deps) == 0 {
		return nil, nil
	}

	// Fetch all dependent issues.
	ids := make([]string, len(deps))
	for i, d := range deps {
		ids[i] = d.depID
	}
	issues, err := GetIssuesByIDsInTx(ctx, tx, ids, nil)
	if err != nil {
		return nil, fmt.Errorf("get dependents: fetch issues: %w", err)
	}
	issueMap := make(map[string]*types.Issue, len(issues))
	for _, iss := range issues {

View on GitHub (pinned to 71377f2769)