Skip to content

Concurrent file_patch calls can both succeed while losing an update #799

Description

@Whxuan0701

Problem

Concurrent file_patch() calls perform an unlocked read-modify-write sequence. Two subagents can both report success while one valid edit is silently lost.

This matters in Conductor/parallel-agent workflows because agents share the same project files and temp/ state. The tool's uniqueness check protects a patch within one snapshot, but not the snapshot itself from becoming stale before the write.

Deterministic reproduction

Tested on main@f6e5657 (macOS 14.6.1, Python 3.13.14):

  1. Create state.txt containing:

    A=0
    B=0
    
  2. Start two threads together:

    • patch A=0 to A=1
    • patch B=0 to B=1
  3. Delay the first read so both calls operate on the same initial snapshot.

Observed:

results == [success, success]
final == "A=0\nB=1\n"  # or the symmetric loss

Both callers claim success, but the final file does not contain both independent edits.

Root cause

file_patch() reads the entire file, computes updated_text, and later opens the path with w. There is no per-path critical section around that transaction.

Expected behavior

For one resolved path, concurrent patches must serialize across both threads and GenericAgent processes. The second patch should read the first patch's committed content, so independent edits are both retained; stale/conflicting edits should fail the existing old_content validation instead of reporting a false success.

Suggested direction

  • Add an in-process per-path mutex.
  • Add an OS-backed lock file keyed by the resolved path for cross-process coordination (flock on POSIX, msvcrt.locking on Windows).
  • Hold both locks across read, uniqueness validation, newline detection, and write.
  • Add thread and spawned-process regression tests.

This is different from #770/#771's file_write failure atomicity. Atomic replacement protects against partial writes; it does not serialize two successful read-modify-write operations.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions