From dc04cab2082c13eba58d07f98e8b7ebafa129e50 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Feb 2026 06:06:10 +0000 Subject: [PATCH] Make config writes atomic via temp file + rename Prevents partial writes and corruption of config.yaml when the process is interrupted mid-write or multiple CLI processes race on the same file. https://claude.ai/code/session_01JKkksiVQuP5HJVUshkmRS6 --- hoard/core/config.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/hoard/core/config.py b/hoard/core/config.py index 70140fd..90f0df1 100644 --- a/hoard/core/config.py +++ b/hoard/core/config.py @@ -1,6 +1,8 @@ from __future__ import annotations import copy +import os +import tempfile from dataclasses import dataclass from pathlib import Path from typing import Any, Dict @@ -250,14 +252,27 @@ def ensure_config_file(path: Path | None = None) -> Path: config_path = path or get_default_config_path() config_path.parent.mkdir(parents=True, exist_ok=True) if not config_path.exists(): - config_path.write_text(yaml.safe_dump(copy.deepcopy(DEFAULT_CONFIG), sort_keys=False)) + save_config(copy.deepcopy(DEFAULT_CONFIG), config_path) return config_path def save_config(config: Dict[str, Any], path: Path | None = None) -> Path: config_path = path or get_default_config_path() config_path.parent.mkdir(parents=True, exist_ok=True) - config_path.write_text(yaml.safe_dump(config, sort_keys=False)) + data = yaml.safe_dump(config, sort_keys=False) + fd, tmp = tempfile.mkstemp(dir=config_path.parent, suffix=".tmp") + try: + with os.fdopen(fd, "w") as f: + f.write(data) + f.flush() + os.fsync(f.fileno()) + os.replace(tmp, config_path) + except BaseException: + try: + os.unlink(tmp) + except OSError: + pass + raise return config_path