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

  1. Inspect the wrapped cause after 'affected by close for <id>:' — it chains the original driver error
  2. Retry the close if the cause was transient; the whole close is transactional and retry-safe
  3. Run schema/migration checks if a dependencies or wisp_dependencies table is reported missing
  4. 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

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


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