@@ -43,27 +43,65 @@ func (e *Executor) move(src, destDir string) error {
4343 return err
4444 }
4545
46+ srcInfo , err := os .Stat (src )
47+ if err != nil {
48+ if os .IsNotExist (err ) {
49+ // Source is already gone — a prior event already moved it.
50+ slog .Debug ("Source file already removed, skipping move" , "src" , src )
51+ return nil
52+ }
53+ return err
54+ }
55+
4656 destPath := e .getDestPath (src , destDir )
4757
48- // Ensure we aren't moving a file into itself or sensitive areas if we can help it
4958 if destPath == src {
5059 return fmt .Errorf ("source and destination are the same: %s" , src )
5160 }
5261
53- // Attempt standard rename
54- err := os .Rename (src , destPath )
55- if err == nil {
62+ // Directories can only be renamed, not copied as a fallback.
63+ if srcInfo .IsDir () {
64+ return e .renameOrSkip (src , destPath )
65+ }
66+
67+ // Attempt standard rename for files.
68+ renameErr := e .renameOrSkip (src , destPath )
69+ if renameErr == nil {
5670 return nil
5771 }
5872
59- // If rename fails (e.g. cross-device), fallback to copy + delete
60- slog .Debug ("Standard rename failed, attempting copy+delete fallback" , "src" , src , "dest" , destPath , "error " , err )
73+ // If rename fails (e.g. cross-device), fallback to copy + delete.
74+ slog .Debug ("Standard rename failed, attempting copy+delete fallback" , "src" , src , "dest" , destPath , "renameError " , renameErr )
6175
6276 if err := e .copyToPath (src , destPath ); err != nil {
77+ slog .Debug ("copyToPath failed" , "src" , src , "dest" , destPath , "error" , err , "isNotExist" , os .IsNotExist (err ))
78+ if os .IsNotExist (err ) {
79+ slog .Debug ("Source file disappeared during fallback copy" , "src" , src )
80+ return nil
81+ }
6382 return fmt .Errorf ("fallback copy failed: %w" , err )
6483 }
6584
66- return os .Remove (src )
85+ // Clean up the source after a successful copy.
86+ if err := os .Remove (src ); err != nil && ! os .IsNotExist (err ) {
87+ return fmt .Errorf ("failed to remove source after copy: %w" , err )
88+ }
89+
90+ return nil
91+ }
92+
93+ // renameOrSkip attempts os.Rename. If the source has already been removed
94+ // (e.g. by a concurrent event), it returns nil instead of an error.
95+ func (e * Executor ) renameOrSkip (src , dest string ) error {
96+ err := os .Rename (src , dest )
97+ if err == nil {
98+ return nil
99+ }
100+ if os .IsNotExist (err ) {
101+ slog .Debug ("Source file disappeared before rename completed" , "src" , src , "dest" , dest )
102+ return nil
103+ }
104+ return err
67105}
68106
69107func (e * Executor ) copy (src , destDir string ) error {
0 commit comments