gastownhall/beads · error
failed to close issue: %w
Error message
failed to close issue: %w
What it means
This error wraps the underlying driver/SQL error returned when the UPDATE that marks an issue as closed fails inside a transaction. closeIssueInTx executes an UPDATE on the issues table setting status=closed, closed_at, close_reason, and session/lock columns; any failure from the database layer is surfaced here. It preserves the driver error via %w so callers can inspect the root cause with errors.Is/As.
Source
Thrown at internal/storage/issueops/close.go:345
affectedIssues, affectedWisps, aerr = AffectedByStatusChangeInTx(ctx, tx, id)
}
if aerr != nil {
return nil, fmt.Errorf("affected by close for %s: %w", id, aerr)
}
now := time.Now().UTC()
// row_lock is rewritten on close so a concurrent reclaim (which also rewrites
// row_lock) collides on this cell and is forced to conflict-and-retry rather
// than silently cell-merging a revert-to-ready over a completed close (see
// lease.go). The lease row is deleted below: a closed issue holds no lease.
result, err := tx.ExecContext(ctx, fmt.Sprintf(`
UPDATE %s SET status = ?, closed_at = ?, updated_at = ?, close_reason = ?, closed_by_session = ?,
row_lock = ?
WHERE id = ? AND status != ?
`, 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 {View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped driver error with errors.Is/As to identify the root cause (context deadline, deadlock, connection refused) and address that first.
- Retry the whole operation with a fresh transaction; the tx is aborted after an exec error so partial retry inside the same tx is impossible.
- Check connectivity and server health (bd dolt push/ping the DB); verify the context timeout is generous enough.
- Verify the schema matches the installed beads version (run bd doctor / migrations) — missing columns cause UPDATE errors.
Example fix
// before
result, err := tx.ExecContext(ctx, closeSQL, args...)
if err != nil {
return nil, fmt.Errorf("failed to close issue: %w", err)
}
// after
result, err := tx.ExecContext(ctx, closeSQL, args...)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, fmt.Errorf("failed to close issue %s: transaction timed out, retry with fresh tx: %w", id, err)
}
return nil, fmt.Errorf("failed to close issue: %w", err)
} Defensive patterns
Strategy: try-catch
Try / catch
if err := store.CloseIssue(ctx, id, actor, reason); err != nil {
var driverErr *mysql.MySQLError // or driver-specific type
if errors.As(err, &driverErr) {
log.Printf("driver error %d on close: %v", driverErr.Number, err)
}
if errors.Is(err, context.DeadlineExceeded) {
// retry once with a fresh context/transaction
}
return err
} Prevention
- Always pass a context with an adequate timeout to close operations.
- Retry the whole transaction on transient driver errors; never retry inside the aborted tx.
- Keep beads and the storage driver/schema versions aligned (run bd doctor).
- Monitor DB connectivity and avoid concurrent writers on the same issue row.
When it happens
Trigger: Any failure of tx.ExecContext running the close UPDATE: connection loss mid-transaction, constraint violation, locked table, malformed row_lock value, or the transaction already having been rolled back or timed out via its context.
Common situations: Database restarted or connection dropped during a long transaction; context deadline exceeded on a slow Dolt/MySQL server; deadlock with another concurrent writer holding locks on the same issue row; schema drift after a version upgrade removing a column like close_reason or closed_by_session.
Related errors
- failed to begin transaction: %w
- failed to commit is_blocked repairs: %w
- db: ChildCounterSQLRepository.NextChildID: probe parent tabl
- db: ChildCounterSQLRepository.NextChildID: read counter for
- db: ChildCounterSQLRepository.NextChildID: scan existing chi
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/f10d3112a97d3449.
Report an issue: GitHub.