gastownhall/beads · error

failed to check issue existence: %w

Error message

failed to check issue existence: %w

What it means

This error wraps a failure of the follow-up SELECT status FROM issues WHERE id = ? that runs when the close UPDATE matched zero rows. That SELECT is needed to distinguish 'already closed' from 'not found'; if the query itself errors (driver, connection, scan), the close is aborted with this wrapped error. The original driver error is preserved via %w.

Source

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

	`, issueTable), types.StatusClosed, now, now, reason, session, freshRowLock(), id, types.StatusClosed)
	if err != nil {
		return nil, fmt.Errorf("failed to close issue: %w", err)
	}

	rows, err := result.RowsAffected()
	if err != nil {
		return nil, fmt.Errorf("failed to get rows affected: %w", err)
	}
	if rows == 0 {
		var status string
		qerr := tx.QueryRowContext(ctx,
			fmt.Sprintf(`SELECT status FROM %s WHERE id = ?`, issueTable), id,
		).Scan(&status)
		if qerr == sql.ErrNoRows {
			return nil, fmt.Errorf("%w: issue %s", storage.ErrNotFound, id)
		}
		if qerr != nil {
			return nil, fmt.Errorf("failed to check issue existence: %w", qerr)
		}
		if types.Status(status) == types.StatusClosed {
			return &CloseResult{IsWisp: isWisp, AlreadyClosed: true}, nil
		}
		return nil, fmt.Errorf("failed to close issue: %s", id)
	}

	// A closed issue holds no lease (no-op for wisps, which are never leased).
	if err := DeleteLeaseInTx(ctx, tx, id); err != nil {
		return nil, err
	}

	if recordEvent {
		if err := RecordEventInTable(ctx, tx, eventTable, id, types.EventClosed, actor, reason); err != nil {
			return nil, fmt.Errorf("failed to record event: %w", err)
		}
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Inspect the wrapped error with errors.Is/As to find the root cause (timeout, connection reset, scan type mismatch).
  2. Retry the close operation in a fresh transaction after confirming the DB is reachable.
  3. Increase the context deadline if timeouts are killing the diagnostic query.
  4. Check server logs for the failing query; verify the issues table schema is intact (bd doctor).
Defensive patterns

Strategy: try-catch

Try / catch

err := store.CloseIssue(ctx, id, actor, reason)
if err != nil && strings.Contains(err.Error(), "failed to check issue existence") {
    if errors.Is(err, context.DeadlineExceeded) || isTransientNet(err) {
        return retryClose(ctx, id, actor, reason, 1)
    }
    return err
}

Prevention

When it happens

Trigger: tx.QueryRowContext(...).Scan() returns a non-ErrNoRows error during the existence check: connection failure inside the transaction, context cancellation, driver error, or datatype mismatch while scanning into the status string.

Common situations: Connection dropped between the failed UPDATE and the diagnostic SELECT; context timeout expiring mid-function; corrupted row data preventing scan; low-level driver bug under load.

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