Skip to content

Commit 7f53363

Browse files
committed
fix(entity-io): prevent concurrent write collisions on same slug
Use tempfile.mkstemp for unique temp files and os.open with O_EXCL for atomic target creation, retrying candidate selection on race. Addresses CodeRabbit review finding: Concurrent writes with the same slug can collide and lose data
1 parent 0605741 commit 7f53363

1 file changed

Lines changed: 25 additions & 6 deletions

File tree

plugins/kaizen/lib/entity_io.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -256,10 +256,29 @@ def write_entity_file(directory, entity):
256256
type_dir.mkdir(parents=True, exist_ok=True)
257257

258258
slug = slugify(entity.get("content", "entity"))
259-
target = unique_filename(type_dir, slug)
260-
tmp = target.with_suffix(".tmp")
261-
262259
content = entity_to_markdown(entity)
263-
tmp.write_text(content, encoding="utf-8")
264-
os.rename(tmp, target)
265-
return target
260+
261+
# Write to a unique temp file first (avoids predictable .tmp collisions)
262+
fd, tmp_path = tempfile.mkstemp(dir=type_dir, suffix=".tmp", prefix=slug)
263+
try:
264+
os.write(fd, content.encode("utf-8"))
265+
os.close(fd)
266+
fd = None
267+
268+
# Atomically claim the target using O_EXCL to detect races
269+
target = unique_filename(type_dir, slug)
270+
try:
271+
claim_fd = os.open(str(target), os.O_CREAT | os.O_EXCL | os.O_WRONLY)
272+
os.close(claim_fd)
273+
except FileExistsError:
274+
# Another writer beat us — re-discover a free name
275+
target = unique_filename(type_dir, slug)
276+
277+
os.rename(tmp_path, target)
278+
return target
279+
except BaseException:
280+
if fd is not None:
281+
os.close(fd)
282+
if os.path.exists(tmp_path):
283+
os.unlink(tmp_path)
284+
raise

0 commit comments

Comments
 (0)