gastownhall/beads · error
affected by close for %s: %w
Error message
affected by close for %s: %w
What it means
Beads throws this when computing which issues would be affected by closing the given id fails. closeIssueInTx calls AffectedByStatusChangeInTx (or the wisp variant) before performing the close UPDATE; if that impact analysis errors, the close is aborted with the id embedded for debugging. The root driver error is wrapped underneath.
Source
Thrown at internal/storage/issueops/close.go:330
return false, "", false, fmt.Errorf("read status from %s: %w", target.table, err)
}
return false, "", false, nil
}
//nolint:gosec // G201: table names come from WispTableRouting (hardcoded constants)
func closeIssueInTx(ctx context.Context, tx DBTX, id string, reason, actor, session string, recordEvent bool) (*CloseResult, error) {
isWisp := IsActiveWispInTx(ctx, tx, id)
issueTable, _, eventTable, _ := WispTableRouting(isWisp)
var affectedIssues, affectedWisps []string
var aerr error
if isWisp {
affectedIssues, affectedWisps, aerr = AffectedByStatusChangeForWispInTx(ctx, tx, id)
} else {
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()View on GitHub (pinned to 71377f2769)
Solutions
- Inspect the wrapped cause after 'affected by close for <id>:' — it chains the original driver error
- Retry the close if the cause was transient; the whole close is transactional and retry-safe
- Run schema/migration checks if a dependencies or wisp_dependencies table is reported missing
- Reduce lock contention (avoid running bulk ops concurrently with closes) and add generous context timeouts for large graphs
Defensive patterns
Strategy: retry
Validate before calling
// Pre-check the dependency tables the impact query needs
for _, t := range []string{"dependencies", "wisp_dependencies"} {
var one int
err := db.QueryRow("SELECT 1 FROM " + t + " LIMIT 1").Scan(&one)
if err != nil && !dberrors.IsTableNotExist(err) {
return fmt.Errorf("%s unreadable before close: %w", t, err)
}
} Try / catch
res, err := CloseIssue(ctx, id, opts)
if err != nil && strings.Contains(err.Error(), "affected by close for") {
if dberrors.IsTransient(err) || errors.Is(err, context.DeadlineExceeded) {
return retryCloseWithBackoff(ctx, id)
}
return fmt.Errorf("impact analysis failed for %s: %w", id, err)
} Prevention
- Add context timeouts proportional to dependency-graph depth before closing hub issues
- Serialize bulk dependency mutations with issue closes to avoid lock contention
- Keep dependencies and wisp_dependencies schemas fully migrated
- Retry closes on transient errors; the transactional design makes retries safe
When it happens
Trigger: CloseIssue / CloseIssueWithoutEventInTx → closeIssueInTx when AffectedByStatusChangeInTx or AffectedByStatusChangeForWispInTx returns an error: dependency-graph queries hitting connection failure, lock timeout, missing dependencies/wisp_dependencies tables (when not gated as optional), or context cancellation while traversing the graph.
Common situations: Large dependency graphs making impact queries slow enough to hit timeouts; concurrent bulk operations contending on dependencies rows; partially provisioned wisp tables; database connectivity loss between the impact query and the close.
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
- failed to begin transaction: %w
- failed to commit is_blocked repairs: %w
- failed to set dolt_force_transaction_commit: %w
- failed to begin regular tx: %w
- failed to begin ignored tx: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/5d55e6fe1df3ffc3.
Report an issue: GitHub.