Skip to content

Atomic writes collide in containers — the temp filename uses the PID, which is always 1 #5596

Description

@Ninthkitten

Prerequisites

  • I searched open issues and discussions and did not find an existing report of this bug.
  • This is not a security vulnerability.
  • I am running the latest code from the dev branch.

Install Method

Docker

Operating System

macOS (Apple Silicon), Docker Desktop

Steps to Reproduce

  1. Run Odysseus in Docker (the app process is PID 1).
  2. Trigger two settings writes that overlap — e.g. save the tool list while a background job persists a preference.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready for reviewDescription complete — ready for maintainer review

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions