Fix _save_state race condition losing last_reflection timestamp

Daily and weekly timers fired simultaneously on Sundays, causing
decay() and reflect() to race on _state.json. Now merges top-level
keys before writing and uses atomic tempfile+rename to prevent
partial reads from triggering silent JSONDecodeError fallbacks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-02-20 00:45:41 -06:00
parent 25792a74f4
commit 7f120f8c5c

View File

@ -622,9 +622,30 @@ class CognitiveMemoryClient:
return {"version": 1, "updated": "", "entries": {}}
def _save_state(self, state: Dict):
"""Write _state.json."""
"""Write _state.json atomically, merging top-level keys to prevent race conditions."""
import tempfile
# Merge with existing state to preserve keys written by concurrent processes
if self.state_path.exists():
try:
existing = json.loads(self.state_path.read_text())
existing.update(state)
state = existing
except (json.JSONDecodeError, OSError):
pass
state["updated"] = datetime.now(timezone.utc).isoformat()
self.state_path.write_text(json.dumps(state, indent=2, default=str))
# Atomic write: write to temp file then rename
fd, tmp_path = tempfile.mkstemp(dir=self.memory_dir, suffix=".tmp")
try:
with os.fdopen(fd, "w") as f:
json.dump(state, f, indent=2, default=str)
os.replace(tmp_path, self.state_path)
except Exception:
try:
os.unlink(tmp_path)
except OSError:
pass
raise
# -------------------------------------------------------------------------
# File I/O