files-community/Files · error · IOException

Failed to move file from {Path} to {destFolder}.

Error message

Failed to move file from {Path} to {destFolder}.

What it means

Thrown by FtpStorageFile.MoveAsync when AsyncFtpClient.MoveFile returns false after a successful connection. The RNFR/RNTO rename did not complete: the destination already exists with skip mode, the source path no longer exists, the user lacks permission, or the server rejected the rename.

Source

Thrown at src/Files.App/Utils/Storage/StorageItems/FtpStorageFile.cs:206

			=> MoveAsync(destinationFolder, desiredNewName, NameCollisionOption.FailIfExists);
		public override IAsyncAction MoveAsync(IStorageFolder destinationFolder, string desiredNewName, NameCollisionOption option)
		{
			return AsyncInfo.Run((cancellationToken) => SafetyExtensions.WrapAsync(async () =>
			{
				using var ftpClient = GetFtpClient();
				if (!await ftpClient.EnsureConnectedAsync())
					throw new IOException($"Failed to connect to FTP server.");

				BaseStorageFolder destFolder = destinationFolder.AsBaseStorageFolder();

				if (destFolder is FtpStorageFolder ftpFolder)
				{
					string destName = $"{ftpFolder.FtpPath}/{Name}";
					FtpRemoteExists ftpRemoteExists = option is NameCollisionOption.ReplaceExisting ? FtpRemoteExists.Overwrite : FtpRemoteExists.Skip;

					bool isSuccessful = await ftpClient.MoveFile(FtpPath, destName, ftpRemoteExists, cancellationToken);
					if (!isSuccessful)
						throw new IOException($"Failed to move file from {Path} to {destFolder}.");
				}
				else
					throw new NotSupportedException();
			}, ((IPasswordProtectedItem)this).RetryWithCredentialsAsync));
		}


		public override IAsyncAction CopyAndReplaceAsync(IStorageFile fileToReplace) => throw new NotSupportedException();
		public override IAsyncAction MoveAndReplaceAsync(IStorageFile fileToReplace) => throw new NotSupportedException();

		public override IAsyncAction RenameAsync(string desiredName)
			=> RenameAsync(desiredName, NameCollisionOption.FailIfExists);
		public override IAsyncAction RenameAsync(string desiredName, NameCollisionOption option)
		{
			return AsyncInfo.Run((cancellationToken) => SafetyExtensions.WrapAsync(async () =>
			{
				using var ftpClient = GetFtpClient();
				if (!await ftpClient.EnsureConnectedAsync())

View on GitHub (pinned to 68c68a58d4)

Solutions

  1. If overwriting is intended, call MoveAsync with NameCollisionOption.ReplaceExisting so FtpRemoteExists.Overwrite is used.
  2. Pre-check the destination with DirectoryExists/FileExists and resolve collisions (rename, skip, or overwrite) at the call site.
  3. Inspect the FluentFTP reply (LastReply) for the 5xx code explaining why MoveFile failed.
  4. Verify the source FtpPath still exists immediately before the move.
  5. Catch IOException, confirm whether a destination collision is the cause, and retry accordingly.

Example fix

// before
await file.MoveAsync(destFolder, file.Name, NameCollisionOption.FailIfExists);

// after
var option = await DestExists(destFolder, file.Name)
    ? NameCollisionOption.ReplaceExisting
    : NameCollisionOption.FailIfExists;
await file.MoveAsync(destFolder, file.Name, option);
Defensive patterns

Strategy: try-catch

Validate before calling

// Resolve destination collisions before moving.
bool exists = await ftpClient.FileExists($"{ftpFolder.FtpPath}/{file.Name}", ct);
var option = exists ? NameCollisionOption.ReplaceExisting : NameCollisionOption.FailIfExists;

Try / catch

try { await file.MoveAsync(destFolder, file.Name, option); }
catch (IOException ex) when (ex.Message.Contains("Failed to move file"))
{ var reply = ftpClient.LastReply; /* inspect 5xx */ throw; }

Prevention

When it happens

Trigger: MoveFile returning false inside MoveAsync. With NameCollisionOption other than ReplaceExisting, FtpRemoteExists.Skip is used, so an existing destination file causes the move to be skipped/rejected. Permission denied, source-not-found, or cross-device rename restrictions also produce false.

Common situations: Moving a file onto a name that already exists without choosing ReplaceExisting; the source file was deleted between listing and move; insufficient write permission at the destination; renaming across different FTP roots the server does not allow.

Related errors


AI-assisted analysis of files-community/Files@68c68a58d4 (2026-08-13). Data as JSON: /api/errors/ba1d1286924c267d. Report an issue: GitHub.