@@ -165,13 +165,57 @@ export class CliManager {
165165 "Moving existing binary to" ,
166166 path . basename ( oldBinPath ) ,
167167 ) ;
168- await fs . rename ( binPath , oldBinPath ) ;
168+ try {
169+ await fs . rename ( binPath , oldBinPath ) ;
170+ } catch ( error ) {
171+ // That's fine since we are trying to move the file anyway
172+ if ( ( error as NodeJS . ErrnoException ) . code !== "ENOENT" ) {
173+ throw error ;
174+ }
175+ this . output . debug (
176+ "Binary already moved by another process, continuing" ,
177+ ) ;
178+ }
169179 }
170180
171181 // Then move the temporary binary into the right place.
172182 this . output . info ( "Moving downloaded file to" , path . basename ( binPath ) ) ;
173183 await fs . mkdir ( path . dirname ( binPath ) , { recursive : true } ) ;
174- await fs . rename ( tempFile , binPath ) ;
184+ try {
185+ await fs . rename ( tempFile , binPath ) ;
186+ } catch ( error ) {
187+ const errCode = ( error as NodeJS . ErrnoException ) . code ;
188+ // On Windows, fs.rename fails if the target exists. On POSIX systems,
189+ // it atomically replaces the target. If another process already wrote
190+ // the binary and it's the correct version, we can consider this a
191+ // success since both processes are installing the same version.
192+ if (
193+ errCode === "EPERM" ||
194+ errCode === "EACCES" ||
195+ errCode === "EBUSY"
196+ ) {
197+ this . output . debug (
198+ "Binary may be in use or already exists, verifying version" ,
199+ ) ;
200+ const existingStat = await cliUtils . stat ( binPath ) ;
201+ if ( existingStat !== undefined ) {
202+ try {
203+ const existingVersion = await cliUtils . version ( binPath ) ;
204+ const expectedVersion = buildInfo . version ;
205+ if ( existingVersion === expectedVersion ) {
206+ this . output . info (
207+ "Binary already installed by another process with correct version, cleaning up temp file" ,
208+ ) ;
209+ await fs . rm ( tempFile , { force : true } ) ;
210+ return binPath ;
211+ }
212+ } catch {
213+ // If we can't verify the version, fall through to throw original error
214+ }
215+ }
216+ }
217+ throw error ;
218+ }
175219
176220 // For debugging, to see if the binary only partially downloaded.
177221 const newStat = await cliUtils . stat ( binPath ) ;
0 commit comments