gastownhall/beads · error

classify child-count target in issues: %w

Error message

classify child-count target in issues: %w

What it means

Beads throws this when the probe 'SELECT 1 FROM issues WHERE id = ?' used to classify a close-policy target fails with a non-'no rows' error. Only sql.ErrNoRows is an acceptable outcome (meaning the id is not a durable issue); any other driver error is wrapped and aborts target classification for the open-children count.

Source

Thrown at internal/storage/issueops/close.go:273

func releaseCloseCheckedSavepoint(ctx context.Context, tx DBTX, name string) error {
	//nolint:gosec // G201: name is generated by createCloseCheckedSavepoint.
	if _, err := tx.ExecContext(ctx, "RELEASE SAVEPOINT "+name); err != nil {
		return fmt.Errorf("release checked close savepoint: %w", err)
	}
	return nil
}

// dependencyTargetColumnForIDInTx returns the typed target column for id,
// preferring a durable row when an id is present in both tables.
func dependencyTargetColumnForIDInTx(ctx context.Context, tx DBTX, id string) (string, error) {
	var found int
	err := tx.QueryRowContext(ctx, "SELECT 1 FROM issues WHERE id = ?", id).Scan(&found)
	if err == nil {
		return "depends_on_issue_id", nil
	}
	if !errors.Is(err, sql.ErrNoRows) {
		return "", fmt.Errorf("classify child-count target in issues: %w", err)
	}
	err = tx.QueryRowContext(ctx, "SELECT 1 FROM wisps WHERE id = ?", id).Scan(&found)
	if err == nil {
		return "depends_on_wisp_id", nil
	}
	if optionalBlockedTable("wisps") && isTableNotExistError(err) {
		return "depends_on_issue_id", nil
	}
	if !errors.Is(err, sql.ErrNoRows) {
		return "", fmt.Errorf("classify child-count target in wisps: %w", err)
	}
	return "depends_on_issue_id", nil
}

// isClosedInTx reports whether the issue (or wisp) identified by id exists and
// is already closed. It probes the issues table then the optional wisps table,
// mirroring IsBlockedInTx's table order.
//

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped driver error after 'classify child-count target in issues:' for the root cause
  2. Restore connectivity / retry the close if the cause was transient
  3. Run schema checks/migrations if issues is reported missing — unlike wisp tables, issues is mandatory
  4. Widen or remove context deadlines if the probe times out
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight: issues is mandatory; verify it exists and is readable
var one int
if err := db.QueryRow("SELECT 1 FROM issues LIMIT 1").Scan(&one); err != nil {
	return fmt.Errorf("issues table not readable (run migrations?): %w", err)
}

Try / catch

count, err := countOpenChildrenInTx(ctx, tx, id)
if err != nil {
	if errors.Is(err, context.DeadlineExceeded) || dberrors.IsConnectionError(err) || dberrors.IsLockTimeout(err) {
		return retryClose(ctx, id)
	}
	return fmt.Errorf("close of %s failed: %w", id, err)
}

Prevention

When it happens

Trigger: countOpenChildrenInTx → dependencyTargetColumnForIDInTx when the issues table is unreadable: missing issues table (error 1146 without optional-table gating applying — issues is not optional), permission denied, connection failure, lock timeout, or context cancellation during the probe.

Common situations: Database connection dropped mid-close; schema so broken the issues table is missing; another process holds locks on issues; context deadline exceeded on a slow server.

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/6a97f9bdb1005e37. Report an issue: GitHub.