Code snippet
tasks = state_manager.get("tasks", {})
tasks[task_id] = updated_task
state_manager.set("tasks", tasks)
What happened?
TaskManager._store_task_result() performs a read-modify-write using separate StateManager.get() and StateManager.set() calls. Each call acquires its own file lock.
When multiple detached tasks update state.json concurrently, both can read the same old state and the later write can discard the other task’s update.
Expected behavior: updating one task preserves every concurrently written task and field.
Proposed solution: add an atomic mutation/update operation to StateManager that holds one lock across load, mutation, and save, then use it for task updates and cleanup.
Acceptance criteria:
- Task-state read-modify-write operations hold a single lock.
- Concurrent task updates do not lose entries or fields.
- Tests reproduce overlapping updates deterministically.
- Existing state-file corruption recovery remains intact.
Code snippet
What happened?
TaskManager._store_task_result()performs a read-modify-write using separateStateManager.get()andStateManager.set()calls. Each call acquires its own file lock.When multiple detached tasks update
state.jsonconcurrently, both can read the same old state and the later write can discard the other task’s update.Expected behavior: updating one task preserves every concurrently written task and field.
Proposed solution: add an atomic mutation/update operation to
StateManagerthat holds one lock across load, mutation, and save, then use it for task updates and cleanup.Acceptance criteria: