gastownhall/beads · error

get issue labels: %w

Error message

get issue labels: %w

What it means

This error wraps a failure from GetLabelsInTx while loading an issue's labels inside the same transaction used to fetch the issue row. The issue row itself was read successfully, but the subsequent label lookup against the label table failed, so the whole getIssueFromTableInTx call aborts with no partial data returned. It exists to preserve context ('which step of issue load failed') while retaining the underlying driver error via %w.

Source

Thrown at internal/storage/issueops/get_issue.go:61

	return optionalBlockedTable(issueTable) && dberrors.IsMissingTable(err, issueTable)
}

func getIssueFromTableInTx(ctx context.Context, tx DBTX, issueTable, labelTable, id string) (*types.Issue, error) {
	//nolint:gosec // G201: issueTable is a hardcoded literal supplied by GetIssueInTx ("issues" or "wisps")
	row := tx.QueryRowContext(ctx, fmt.Sprintf(`SELECT %s FROM %s %s WHERE id = ?`,
		IssueSelectColumns, issueTable, sqlbuild.LeaseJoin(issueTable)), id)
	issue, err := ScanIssueFrom(row)
	if err == sql.ErrNoRows || missingOptionalIssueTable(err, issueTable) {
		return nil, storage.ErrNotFound
	}
	if err != nil {
		return nil, fmt.Errorf("get issue: %w", err)
	}

	// Fetch labels in the same transaction to avoid MaxOpenConns=1 deadlock.
	labels, err := GetLabelsInTx(ctx, tx, labelTable, id)
	if err != nil {
		return nil, fmt.Errorf("get issue labels: %w", err)
	}
	issue.Labels = labels

	return issue, nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped cause (errors.Unwrap / %v chain) to see the real driver error from GetLabelsInTx
  2. Verify the label table exists and matches the current schema (run bd migration/doctor checks)
  3. Check for connection pool starvation: ensure the same tx is used for labels (it already is) and no external long transaction holds locks
  4. Retry the operation; if transient lock timeouts persist, check for other processes holding the DB

Example fix

// before
labels, err := GetLabelsInTx(ctx, tx, labelTable, id)
if err != nil {
	return nil, fmt.Errorf("get issue labels: %w", err)
}
// after
labels, err := GetLabelsInTx(ctx, tx, labelTable, id)
if err != nil {
	return nil, fmt.Errorf("get issue labels for %s: %w", id, err) // include issue id for debugging
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before calling GetIssueInTx, verify DB reachability and schema
if err := db.PingContext(ctx); err != nil {
	return fmt.Errorf("database unreachable: %w", err)
}
var n int
if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM issue_labels LIMIT 1`).Scan(&n); err != nil {
	return fmt.Errorf("label table missing or unreadable: %w", err)
}

Try / catch

issue, err := GetIssueInTx(ctx, tx, id)
if err != nil {
	if errors.Is(err, context.DeadlineExceeded) {
		// retry with fresh context / longer timeout
	}
	var derr *driver.Error
	if errors.As(err, &derr) {
		log.Printf("label fetch driver error: %v", derr)
	}
	return fmt.Errorf("load issue %s: %w", id, err)
}

Prevention

When it happens

Trigger: Calling GetIssueInTx or getJournalIssueInTx when the labels table is missing/corrupted, the transaction has been invalidated by a prior error or context cancellation, the underlying SQL query in GetLabelsInTx fails (syntax/lock timeout/connection drop), or the tx handle is dead due to the MaxOpenConns=1 pool being starved.

Common situations: Schema drift after upgrading beads where label tables weren't migrated; database locked by another long-running transaction (common with Dolt/SQLite embedded mode); context deadline exceeded during large batch reads; corrupted database file.

Related errors


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