gastownhall/beads · error

get issue: %w

Error message

get issue: %w

What it means

getIssueFromTableInTx failed while executing the hydration SELECT (issue row joined with lease info) for a reason other than no-rows or a missing optional table; the error is wrapped as "get issue". This is a database-level failure, not an absent issue — check the wrapped driver error.

Source

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

// missingOptionalIssueTable reports whether err is the absence of the optional
// issue plane the hydration query just read. The hydration FROM clause also
// carries sqlbuild.LeaseJoin, so a blanket table-not-exist check here folds a
// missing leases table into "row absent" — a wrong answer, not an empty one.
func missingOptionalIssueTable(err error, issueTable string) bool {
	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. Read the wrapped driver error to identify the root cause
  2. Run pending schema migrations so issues/leases/labels match expectations
  3. Retry the transaction if the error was transient
  4. Confirm the transaction is still active before the call

Example fix

// before
issue, err := issueops.GetIssueInTx(ctx, tx, id) // fails: unknown column in 'issues'
// after
if err := migrate(ctx, db); err != nil { // align schema with binary
    return err
}
issue, err := issueops.GetIssueInTx(ctx, tx, id)
Defensive patterns

Strategy: retry

Validate before calling

// ensure the non-optional tables exist before lookup
for _, tbl := range []string{"issues", "leases", "labels"} {
    if _, err := tx.QueryContext(ctx, fmt.Sprintf(`SELECT 1 FROM %s LIMIT 1`, tbl)); err != nil {
        return fmt.Errorf("table %s unavailable: %w", tbl, err)
    }
}

Try / catch

issue, err := issueops.GetIssueInTx(ctx, tx, id)
if err != nil && !errors.Is(err, storage.ErrNotFound) {
    if isTransient(err) {
        return retryGetIssue(ctx, tx, id)
    }
    log.Printf("hydration query for %s failed: %v", id, err)
    return err
}

Prevention

When it happens

Trigger: ScanIssueFrom/QueryRow returns a driver error: table missing (non-optional tables like issues/leases), schema mismatch, connection failure, or aborted transaction.

Common situations: Schema drift after upgrade (columns added/renamed); Dolt server connection dropped mid-transaction; missing non-optional table such as leases in a partially migrated database.

Understand the failure class

Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.

Related errors


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