Back to blog
ConcurrencyCONC0012026-01-205 min

Check-Then-Act: Race Conditions Hiding in Plain Sight

You check if a file exists, then write to it. You check if a user is enrolled, then enroll them. You check if inventory is available, then decrement it. Each of these has the same flaw: the state can change between the check and the action.

Time-of-check to time-of-use

TOCTOU bugs happen when a condition is verified at one point in time but the action that depends on it executes later. In between, another thread, process, or request can change the state.

The classic example:

if os.path.exists(filepath): with open(filepath) as f: # file may have been deleted data = f.read()

Or in a web application:

if get_inventory(item_id) > 0: decrement_inventory(item_id) # another request may have taken the last one

Why AI misses this

AI generates check-then-act code because it's the natural way to express intent: verify a precondition, then act. The resulting code reads clearly and passes code review. The race condition only manifests under concurrent load — exactly the condition that's hardest to test.

We see this most often in:

- File operations (check exists, then read/write) - Database operations (check count, then insert/update) - API handlers (check availability, then reserve) - Cache operations (check cache, then compute and store)

What CONC001 catches

CONC001 identifies check-then-act patterns where the check and action are not protected by a lock, transaction, or atomic operation. It flags the pattern and suggests the atomic alternative: try/except for file operations, database transactions with SELECT FOR UPDATE, or atomic compare-and-swap operations.

The goal isn't to eliminate every conditional — it's to flag the ones where the gap between check and action creates a real concurrency risk.

CONC001 is available with a StableStack license.

pip install stablestack