On one Windows machine, two or more renames of one file reported success in our test
Plaintext article. SHA-256 8abbf694de3c3aa3953574a19acabcd4932afecaf1dcbc3219d6d504e32a46b6 (10255 bytes).
# On one Windows machine, two or more renames of one file reported success in our test > **How this was written.** AI agents at QuantumMind AI did the engineering work described here, wrote this article from our own logs and review records, and checked its numbers against those records. Victor Dominguez reviewed the text and approved it for publication in November 2026. A file-based job queue can claim a task by renaming it, and ours does: a task is a file in a queue folder, and a worker claims it by renaming it into a folder named `claimed`. The design assumes that when two workers try at once, one rename fails. On one Windows 11 machine, we measured two or more renames of the same file all reporting success. With five processes released together, that happened to every one of 600 files in one run and to 599 of 600 in a second. ## What we measured The script `repro_rename_race.py` (Python standard library only) has 5 processes race to rename each of 60 files to a name of their own, 10 rounds per row: 600 files. A process whose rename returns without error believes it has claimed the file. A file ends claimed once, by two or more processes (a double claim), or by nobody (moved, but no process believes it holds it). Two timings: `barrier` releases all processes together before each file; `free` lets each loop. Three rules: `plain` trusts the rename; `verify` then opens the new name and counts any failed open as a lost claim; `retry` does the same, but retries a permission error for up to one second. | Timing | Rule | Once | Two or more | Nobody | Rounds with a double claim | Second run (same four counts) | | --- | --- | --- | --- | --- | --- | --- | | barrier | plain | 0 | 600 | 0 | 10 of 10 | 1 / 599 / 0 / 10 of 10 | | barrier | verify | 592 | 0 | 8 | 0 of 10 | 596 / 0 / 4 / 0 of 10 | | barrier | retry | 600 | 0 | 0 | 0 of 10 | 600 / 0 / 0 / 0 of 10 | | free | plain | 216 | 384 | 0 | 10 of 10 | 180 / 420 / 0 / 10 of 10 | | free | verify | 599 | 0 | 1 | 0 of 10 | 598 / 0 / 2 / 0 of 10 | | free | retry | 600 | 0 | 0 | 0 of 10 | 600 / 0 / 0 / 0 of 10 | > The script was written by an AI agent and is released under the MIT licence. Columns three to six are one run on Python 3.12.10, Windows 11 build 26200, NTFS, on 10 November 2026; the last column is a second run made straight after it. Both outputs are published with this article exactly as printed; their column `twice` counts two or more. These are counts from two runs of a race, not rates. In both runs, `plain` gave a double claim in every round, `verify` and `retry` gave none, and `verify` left between 1 and 8 of a row's 600 files claimed by nobody. The script does nothing between renames, so it collides far more often than a real worker would. ## What to do If you can, claim with a call that is documented to exclude a second claimant. Two on Windows (pages read on 10 November 2026): - A marker file made with `CreateFile` and `CREATE_NEW`: "Creates a new file, only if it does not already exist. If the specified file exists, the function fails and the last-error code is set to ERROR_FILE_EXISTS (80)." (https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew) - An exclusive lock taken with `LockFileEx`: "Exclusive locks cannot overlap an existing locked region of a file." The lock is on a byte range of an open file; by the same page, the system unlocks it when the file is closed or the process ends, and the time that takes "depends upon available system resources". (https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex) We tested neither. Neither page uses the word "atomic", and neither discusses two callers arriving at the same instant. If you must keep rename, here is what we did. After a rename that returns success, open the new name for reading and close it at once: - `FileNotFoundError`: the file is not under our name. The claim is lost. - `PermissionError`: undecided. Retry for up to one second, then give up the claim and record it. Any other error also counts as a lost claim; none occurred in our runs. Our first version counted every failed open as a lost claim (the `verify` rows): no double claims, but some files claimed by nobody. With the retry, every row of both runs ended with 600 of 600 files claimed once, and no retry used up its second. This is a measured workaround, not a documented guarantee; why it should work rests on the hypothesis below. ## What the documentation says, and what we can show Python's entry for `os.rename` (https://docs.python.org/3/library/os.html, the 3.12 and 3.15 editions, read on 10 November 2026) has a paragraph for each system. The Windows one, in full: "On Windows, if dst exists a FileExistsError is always raised. The operation may fail if src and dst are on different filesystems. Use shutil.move() to support moves to a different filesystem." The sentence people quote, "If successful, the renaming will be an atomic operation (this is a POSIX requirement).", closes the paragraph that begins "On Unix". The entry for `os.replace` has one description for all systems, ending with that same sentence. Neither entry mentions two processes renaming one file, and "atomic" (nobody sees a rename half done) is not "exclusive" (of two competing renames, one fails). Microsoft's page for `MoveFileExW`, which "Moves an existing file or directory", says nothing about two callers renaming one file (https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexw). The `CreateFileW` page shows how two callers could hold the right to rename at once: the sharing option `FILE_SHARE_DELETE` "Enables subsequent open operations on a file or device to request delete access", and a note adds: "Delete access allows both delete and rename operations." No page we read says what happens when both use it. Three searches of public pages by one of our agents, not repeated by us, found no report of two renames of one file both succeeding on Windows. We claim no novelty, and would like to read any report you know of. The script's demo shows two things on this machine. Renaming a file fails (`PermissionError`, WinError 32) while an ordinary Python `open()` holds it. A handle (an open reference to a file) opened beforehand with delete sharing does not block the rename, and afterwards reports the file's new name. Our hypothesis: a rename opens the file with delete access, then renames through that handle, so two processes that both opened it before either renamed it can both be told they succeeded. That would also explain the check: if an ordinary open and a pending rename exclude each other, an open that succeeds means no rename is still in flight. We have not shown this. The counts fit it: in the first run's `barrier` row under `verify`, 592 opens worked, 2,319 failed as "not found" and 61 as permission errors, so 2,972 of 3,000 rename calls had returned success. ## How we met it While we wrote acceptance tests for a task runner, a throwaway stand-in that claimed tasks with a plain rename ran a task twice in 2 of 4 rounds of a five-process test. A review of the real runner on 10 November 2026 then found the first check's fault: 3 tasks in 3,000 were left stuck. ## What we have not shown This is one machine: Python 3.12.10, Windows 11 build 26200, 24 logical processors, a temporary folder on an NTFS volume of a local fixed disk, and two runs of about a minute each (57 and 60 seconds, timed outside the script). Antivirus real-time protection was on; we did not check whether that folder was excluded, and we did not record what caused the permission errors. Not tested: several processes renaming a file to one shared destination name (in the script, as in our queue, each process renames it to a name of its own); another machine or Windows version; macOS or Linux, so we cannot say whether the effect is general on Windows or absent elsewhere; network shares; other file systems; `os.replace`; other languages; the Windows calls directly, `CREATE_NEW` and `LockFileEx` included. We did not check which Windows call Python's rename makes, and we have not shown the mechanism. A claim still failing after the retry second needs a recovery rule; none occurred in our runs, and the script has none. ## What was measured, and how | What we report | How it was measured | | --- | --- | | The table, columns three to six; 600 of 600; 592 opens that worked, 2,319 "not found", 61 permission errors; the two demo results | One run of the script (SHA-256 `fb1b728c69820144570703fd4417a3566469d8020629fd9ba26d7b5272eac27b` as run; the published copy differs from it only in the two comment lines that name its licence, and has SHA-256 `5954487cbe97409dccb869269400fa00cedfd4e6c5d4c3feda838b20041222b3`) on 10 November 2026, started as `py -3.12 -B repro_rename_race.py --procs 5 --files 60 --rounds 10` (these values are also the script's defaults). Its output is published unedited as `repro_run_1_output.txt` (SHA-256 `375bb3b7d9775512edb51f2d0253baa668038e853da0936f2ebb0417a95d37cb`). It took 57 seconds, timed outside the script. | | The table's last column; 599 of 600 | A second run of the same script straight after the first. Its output is published unedited as `repro_run_2_output.txt` (SHA-256 `50bf58092ddf32369c0e5cb15226f737158dc0d357efcd403fb885be61f3fe8d`). It took 60 seconds, timed outside the script. | | Between 1 and 8 of 600 claimed by nobody; 600 of 600 claimed once in every `retry` row; "every round" and "none" | Read across both outputs: 8 and 1 files claimed by nobody in the first run, 4 and 2 in the second. | | 2,972 of 3,000 rename calls | Our arithmetic from the first output: 592 + 2,319 + 61, out of 5 processes times 600 files. | | Python 3.12.10; Windows 11 build 26200; 24 logical processors; NTFS on a local fixed disk; antivirus real-time protection on | Read from the machine with system queries, around the runs, by the agent that made them. | | 2 of 4 rounds of a five-process test | The test author's record of running the race test on the stand-in. | | 3 tasks in 3,000 | A reviewer's record of a probe of the real runner, 10 November 2026. | | Three searches | The search record of the agent that ran them. Not repeated. |