Yeda AI Tips · #121

Español

That Innocent if exists Check Is a Race Condition

That innocent-looking if exists check is a race condition. Between the moment your code checks whether a directory exists and the moment it acts on the answer, another process — or another thread, another container replica, another CI job on the same runner — can create it. Your code then crashes on the very case you thought you handled.

The bug, in four lines

import os

if not os.path.exists(path):   # check
    os.makedirs(path)          # act — but the world changed in between

If a second process creates path after your check and before your makedirs, the call raises FileExistsError. The check didn't prevent the failure; it just made the failure rare enough to reach production. Security folks have a name for this class of bug: CWE-367, Time-of-check Time-of-use (TOCTOU) — "the resource's state can change between the check and the use in a way that invalidates the results of the check."

The fix, in one line

os.makedirs(path, exist_ok=True)

With exist_ok=True (available since Python 3.2), an already-existing target directory is simply not an error — the docs are explicit: "If exist_ok is False (the default), a FileExistsError is raised if the target directory already exists." The underlying mkdir system call either creates the directory or reports it exists — there is no check-then-act gap left in your code for another process to slip into. You deleted a line and a bug.

The pathlib spelling is just as clean:

from pathlib import Path

Path(path).mkdir(parents=True, exist_ok=True)

parents=True creates missing ancestors (like mkdir -p); exist_ok=True suppresses FileExistsError — unless the path exists and is not a directory, in which case it still raises. That's a caveat worth knowing: a stray file named like your directory will still fail loudly, which is exactly what you want.

LBYL vs. EAFP

The check-then-act pattern has a name: LBYL — "look before you leap." Python's glossary warns about it directly: "In a multi-threaded environment, the LBYL approach can risk introducing a race condition between 'the looking' and 'the leaping.'" The idiomatic alternative is EAFP — "easier to ask for forgiveness than permission": attempt the operation and handle the failure, instead of predicting it.

You wroteReplace with
if not os.path.exists(p): os.makedirs(p)os.makedirs(p, exist_ok=True)
if not p.exists(): p.mkdir()p.mkdir(parents=True, exist_ok=True)
if key in d: v = d[key]try/except KeyError or d.get(key)
if os.path.exists(f): open(f)try: open(f) except FileNotFoundError
"does the file NOT exist? then create it"open(f, "x") — exclusive creation, fails if it exists

The rule generalizes: when a built-in already encodes the fallback, let it do the work. dict.get, dict.setdefault, getattr(obj, name, default), contextlib.suppress — each replaces a check-then-act pair with a single atomic-from-your-code's-view operation.

Power-user notes

Resources

Building an AI feature? Yeda AI designs, audits, and ships production LLM systems.

Talk to us · Read the blog