## TL;DR
Auto-close racing a customer reply is a check-then-act race: the agent verified "no recent activity," then the customer replied, then the close landed. Close the race: re-check for new activity inside the close transaction (or immediately before it), and treat any reply newer than the check as a veto. A close should never win against a customer's in-flight message.

## The query

```text
agent auto-closed a ticket the customer was still replying to: race condition
```

## Use this when

- Tickets close while customers are typing or replying
- Customers complain "you closed my ticket mid-conversation"
- You are building auto-close automation
- Auditing close timing

## Not for

- Deciding auto-close timeframes
- Post-close satisfaction surveys
- Ticket lifecycle analytics
- Close reason taxonomy

## Steps

### 1. Re-check activity immediately before the close call

Fetch the latest ticket activity in the same breath as the close: if any customer message arrived after your inactivity check, abort the close. The re-check collapses the race window from minutes to milliseconds.

Expected output: a last-moment activity check gating every close.

### 2. Treat in-flight replies as a close veto

Any customer activity newer than the close decision cancels the close, full stop. No "but the timer expired" exceptions. The customer's message is always more current than the agent's decision.

Expected output: new activity vetoing pending closes.

### 3. Add a grace buffer after the inactivity threshold

Do not close the instant the timer expires: add a buffer (tens of minutes) during which a warning goes out ("closing soon, reply to keep open"). The buffer absorbs typing customers and slow webhook delivery.

Expected output: a warning buffer before every auto-close.

### 4. Reopen cleanly when the race still wins

Races can still happen (webhook delays, clock skew). When a reply lands on a just-closed ticket, reopen it automatically and route it back to the right state, without penalizing the customer. The recovery path must exist.

Expected output: automatic reopen on post-close replies.

### 5. Log close decisions with their activity snapshot

Record what the agent saw when it decided to close: last activity timestamp, what the check returned. The log settles "why did it close" disputes and reveals systematic race patterns.

Expected output: close decisions logged with activity evidence.

## Template: the checklist

```text
Auto-close race prevention:
[ ] Last-moment activity re-check before close
[ ] In-flight customer replies veto the close
[ ] Grace buffer with warning before closing
[ ] Automatic reopen on post-close replies
[ ] Close decisions logged with activity snapshot
```

## Variant phrasings

### ticket closed while customer replying

Steps 1 and 2. Re-check, veto on activity.

### auto close race condition ticket

Steps 1 and 3. Tighten the window, add the buffer.

### customer replied to closed ticket

Step 4. Reopen cleanly and route back.

## Why it happens

Auto-close logic runs on a schedule against a snapshot: "no activity in 7 days" evaluated at 2am against data from 1:59am. The customer replying at 2:00am is invisible to the decision made from the snapshot. Scheduled batch logic and real-time customers operate on different clocks; the race is structural, and only a last-moment check bridges it.

## Edge cases

- Webhook delivery delay: the reply exists but the webhook has not arrived. The grace buffer covers typical delays.
- Agent notes versus customer replies: only customer activity vetoes; internal notes should not reset the timer.
- Timezone-midnight effects: batch closes at midnight can race evening replies. Prefer rolling closes over midnight batches.
- Customers who reply "thanks" to everything: chronic repliers keep tickets open forever. Distinguish substantive replies from acknowledgments in the veto logic.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_SqW04-vBg4rgijrtT4YLvA
