Prerequisites
Install Method
Docker
Operating System
macOS (Apple Silicon), Docker Desktop
Steps to Reproduce
- Run Odysseus in Docker (the app process is PID 1).
- Trigger two settings writes that overlap — e.g. save the tool list while a background job persists a preference.
- Watch
POST /api/tools and the settings file.
Expected Behaviour
Both writes complete. core/atomic_io.py builds a per-writer temp file, so concurrent writers never touch the same path.
Actual Behaviour
The temp filename is <file>.tmp.<pid>. In a container the app is always PID 1 and threads share it, so every concurrent save uses the same temp file. One writer renames it away; the other's rename raises FileNotFoundError.
Observed here as:
- rows of intermittent
500s on POST /api/tools (mixed 500/200 in the same minute)
- agent-level selections "jumping back" to the previous value
default_model changes that never stick — the losing write silently resurrects the old value
The PID is only unique across processes. In a single-process container it is a constant, so the "unique temp name" is not unique at all.
Logs / Screenshots
POST /api/tools HTTP/1.1" 500 Internal Server Error
POST /api/tools HTTP/1.1" 200 OK
POST /api/tools HTTP/1.1" 500 Internal Server Error
Additional Information
Fix running here: append a uuid4 fragment to the temp name in all writers (core/atomic_io.py both paths, plus the prefs _save). The intermittent 500s stopped immediately and settings began sticking.
Follow-up hardening we also run, offered separately if wanted: a module-level lock plus a merge-only-given-keys save (update_settings(patch)), so a stale in-memory snapshot can never resurrect keys another writer just changed.
Related but distinct: #5509 is the same class of problem (lost update on a shared state file) with a different root — that one is a read-modify-write race, this one is a temp-path collision.
Are you willing to submit a fix?
Yes — the fix described above is running in production here and I can open a PR.
Prerequisites
devbranch.Install Method
Docker
Operating System
macOS (Apple Silicon), Docker Desktop
Steps to Reproduce
POST /api/toolsand the settings file.Expected Behaviour
Both writes complete.
core/atomic_io.pybuilds a per-writer temp file, so concurrent writers never touch the same path.Actual Behaviour
The temp filename is
<file>.tmp.<pid>. In a container the app is always PID 1 and threads share it, so every concurrent save uses the same temp file. One writer renames it away; the other's rename raisesFileNotFoundError.Observed here as:
500s onPOST /api/tools(mixed 500/200 in the same minute)default_modelchanges that never stick — the losing write silently resurrects the old valueThe PID is only unique across processes. In a single-process container it is a constant, so the "unique temp name" is not unique at all.
Logs / Screenshots
Additional Information
Fix running here: append a
uuid4fragment to the temp name in all writers (core/atomic_io.pyboth paths, plus the prefs_save). The intermittent 500s stopped immediately and settings began sticking.Follow-up hardening we also run, offered separately if wanted: a module-level lock plus a merge-only-given-keys save (
update_settings(patch)), so a stale in-memory snapshot can never resurrect keys another writer just changed.Related but distinct: #5509 is the same class of problem (lost update on a shared state file) with a different root — that one is a read-modify-write race, this one is a temp-path collision.
Are you willing to submit a fix?
Yes — the fix described above is running in production here and I can open a PR.