Repository: RocketChat/Rocket.Chat
Pull request #35905: fix: file deletion called twice and ignoring errors with FileSystem storage type
Author: login: cardoso
Labels: name: stat: ready to merge
description: PR tested and approved waiting for merge
name: stat: QA assured
description: Means it has been tested and approved by a company insider
Created: 2025-04-29T18:52:30Z | Merged: 2025-05-20T00:02:18Z

<!-- This is a pull request template, you do not need to uncomment or remove the comments, they won't show up in the PR text. -->

<!-- Your Pull Request name should start with one of the following tags
  feat: Adding a new feature
  refactor: A code change that doesn't change behavior (it doesn't add anything and doesn't fix anything)
  fix: For bug fixes that affect the end-user
  chore: For small tasks
  docs: For documentation
  ci: For updating CI configuration
  test: For adding tests
  i18n: For updating any translations
  regression: Issues created/reported/fixed during the development phase. kind of problem that never existed in production and that we don't need to list in a changelog for the end user
-->

<!-- Checklist!!! If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code. 
  - I have read the Contributing Guide - https://github.com/RocketChat/Rocket.Chat/blob/develop/.github/CONTRIBUTING.md#contributing-to-rocketchat doc
  - I have signed the CLA - https://cla-assistant.io/RocketChat/Rocket.Chat
  - Lint and unit tests pass locally with my changes
  - I have added tests that prove my fix is effective or that my feature works (if applicable)
  - I have added necessary documentation (if applicable)
  - Any dependent changes have been merged and published in downstream modules
-->

## Proposed changes (including videos or screenshots)
<!--
  Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request.
  If it fixes a bug or resolves a feature request, be sure to link to that issue below.
  This description won't be displayed to our end users in the release notes, so feel free to add as much technical context as needed.
  If the changes introduced in this pull request must be presented in the release notes, make sure to add a changeset file. Check our guidelines for adding a changeset to your pull request: https://developer.rocket.chat/contribute-to-rocket.chat/modes-of-contribution/participate-in-rocket.chat-development/development-workflow#4.-adding-changeset-to-your-pull-request 
-->

Removes the call to fs.stat to check if the file existed and is a file (using isFile()) before attempting to delete it. This was done asynchronously and, if the file existed and was a file, it would then call fs.unlink to delete it.

Instead, the code now directly attempts to delete the file using unlink(path). If the file does not exist (ENOENT), the error is safely ignored.

This change also helps prevent race conditions where the file could be deleted between the stat and unlink calls.

Also fixes the delete method being called twice.

## Issue(s)
<!-- Link the issues being closed by or related to this PR. For example, you can use #594 if this PR closes issue number 594 -->

https://rocketchat.atlassian.net/browse/SUP-745

## Steps to test or reproduce
<!-- Mention how you would reproduce the bug if not mentioned on the issue page already. Also mention which screens are going to have the changes if applicable -->

## Further comments
<!-- If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc... -->


Diff:
diff --git a/.changeset/young-ants-applaud.md b/.changeset/young-ants-applaud.md
new file mode 100644
index 0000000000000..4c500b0fb2744
--- /dev/null
+++ b/.changeset/young-ants-applaud.md
@@ -0,0 +1,5 @@
+---
+"@rocket.chat/meteor": patch
+---
+
+Fixes file deletion being called twice and ignoring errors with FileSystem storage type.
diff --git a/apps/meteor/.mocharc.js b/apps/meteor/.mocharc.js
index 4a47aa3a52d65..78a2a22611a5b 100644
--- a/apps/meteor/.mocharc.js
+++ b/apps/meteor/.mocharc.js
@@ -27,6 +27,7 @@ module.exports = {
 		'lib/callbacks.spec.ts',
 		'server/lib/ldap/*.spec.ts',
 		'server/lib/ldap/**/*.spec.ts',
+		'server/ufs/*.spec.ts',
 		'ee/server/lib/ldap/*.spec.ts',
 		'ee/tests/**/*.tests.ts',
 		'ee/tests/**/*.spec.ts',
diff --git a/apps/meteor/server/ufs/ufs-local.spec.ts b/apps/meteor/server/ufs/ufs-local.spec.ts
new file mode 100644
index 0000000000000..b2eba7bf473e5
--- /dev/null
+++ b/apps/meteor/server/ufs/ufs-local.spec.ts
@@ -0,0 +1,115 @@
+import fs from 'fs';
+
+import { expect } from 'chai';
+import { before, beforeEach, afterEach, describe, it } from 'mocha';
+import proxyquire from 'proxyquire';
+import sinon from 'sinon';
+import type { SinonStub } from 'sinon';
+
+// import { Config } from './ufs-config';
+// import { UploadFS } from './ufs';
+import type { LocalStore as LocalStoreClass } from './ufs-local';
+
+const fakeCollection = {
+	removeById: sinon.stub(),
+	findOne: sinon.stub(),
+};
+
+// Proxyquire chain to mock all Meteor dependencies
+const ufsFilterMock = proxyquire.noCallThru().load('./ufs-filter', {
+	'meteor/meteor': {},
+	'meteor/check': {},
+	'meteor/mongo': {},
+	'meteor/npm-mongo': {},
+});
+
+const { UploadFS } = proxyquire.noCallThru().load('./ufs', {
+	'meteor/meteor': {},
+	'meteor/check': {},
+	'meteor/mongo': {},
+	'meteor/npm-mongo': {},
+	'./ufs-filter': ufsFilterMock,
+	'./ufs-store': {
+		Store: Object,
+	},
+});
+
+// Create the UploadFS mock object as a callable function with properties
+// function UploadFS() {
+// 	return UploadFS;
+// }
+// Object.assign(UploadFS, {
+// 	store: {},
+// 	config: new Config(),
+// 	addStore: sinon.stub(),
+// 	getStore: sinon.stub().returns(undefined),
+// 	getTempFilePath: sinon.stub().returns('/tmp/mockfile'),
+// });
+// The module must export { UploadFS: ... }
+const ufsMock = { UploadFS };
+const ufsMockModule = { UploadFS };
+const ufsIndexMock = { UploadFS };
+
+const ufsStoreMockRaw = proxyquire.noCallThru().load('./ufs-store', {
+	'meteor/meteor': {},
+	'meteor/check': {},
+	'meteor/mongo': {},
+	'meteor/npm-mongo': {},
+	'./ufs': ufsMockModule,
+	'./ufs-filter': ufsFilterMock,
+	'./index': ufsIndexMock,
+});
+// Patch ufsStoreMock to export all possible ways (default, named, etc)
+const ufsStoreMock = Object.assign({}, ufsStoreMockRaw, { default: ufsStoreMockRaw, ufsStoreMock: ufsStoreMockRaw });
+const localStoreProxy = proxyquire.noCallThru().load('./ufs-local', {
+	// 'mkdirp': sinon.stub(),
+	'meteor/meteor': {},
+	'meteor/check': {},
+	'meteor/mongo': {},
+	'meteor/npm-mongo': {},
+	'./ufs': ufsMock,
+	'./ufs-store': ufsStoreMock,
+	'./index': ufsIndexMock,
+});
+
+const { LocalStore } = localStoreProxy as {
+	LocalStore: typeof LocalStoreClass;
+};
+
+describe('LocalStore', () => {
+	let store: LocalStoreClass;
+	let unlinkStub: SinonStub;
+
+	before(() => {
+		fakeCollection.removeById.resolves();
+		fakeCollection.findOne.resolves({ _id: 'test', name: 'file.txt' });
+		store = new LocalStore({ name: 'test', collection: fakeCollection as any, path: '/tmp/ufs-local' });
+	});
+
+	beforeEach(() => {
+		unlinkStub = sinon.stub(fs.promises, 'unlink').resolves();
+		fakeCollection.removeById.resetHistory();
+		fakeCollection.findOne.resetHistory();
+	});
+
+	afterEach(() => {
+		unlinkStub.restore();
+	});
+
+	it('should not throw if file does not exist (ENOENT)', async () => {
+		unlinkStub.rejects(Object.assign(new Error('not found'), { code: 'ENOENT' }));
+		await expect(store.delete('test')).to.be.fulfilled;
+		// Should only call unlink once
+		expect(unlinkStub.calledOnce).to.be.true;
+	});
+
+	it('should throw if unlink fails with non-ENOENT error', async () => {
+		unlinkStub.rejects(Object.assign(new Error('fail'), { code: 'EACCES' }));
+		await expect(store.delete('test')).to.be.rejectedWith('fail');
+	});
+
+	it('should call findOne to get file info', async () => {
+		await store.delete('test');
+		expect(fakeCollection.findOne.calledWith('test')).to.be.true;
+	});
+});
diff --git a/apps/meteor/server/ufs/ufs-local.ts b/apps/meteor/server/ufs/ufs-local.ts
index e054cc54d8728..b8609f197fe16 100644
--- a/apps/meteor/server/ufs/ufs-local.ts
+++ b/apps/meteor/server/ufs/ufs-local.ts
@@ -1,5 +1,6 @@
 import fs from 'fs';
-import { stat, unlink } from 'fs/promises';
+import { unlink } from 'fs/promises';
+import { isNativeError } from 'util/types';
 
 import type { IUpload } from '@rocket.chat/core-typings';
 import mkdirp from 'mkdirp';
@@ -68,18 +69,14 @@ export class LocalStore extends Store {
 
 		this.delete = async (fileId, options) => {
 			const path = await this.getFilePath(fileId);
-
 			try {
-				if (!(await stat(path)).isFile()) {
-					return;
+				await unlink(path);
+			} catch (error) {
+				if (!isNativeError(error) || !('code' in error) || !(error.code === 'ENOENT')) {
+					throw error;
 				}
-			} catch (_e) {
-				// FIXME(user) don't ignore, rather this block shouldn't run twice like it does now
-				return;
 			}
-
-			await unlink(path);
-			await this.removeById(fileId, { session: options?.session });
+			await this.removeById(fileId, { session: options?.session }, true);
 		};
 
 		this.getReadStream = async (fileId: string, file: IUpload, options?: { start?: number; end?: number }) => {
diff --git a/apps/meteor/server/ufs/ufs-store.ts b/apps/meteor/server/ufs/ufs-store.ts
index 32caadd4a9db8..00979b8d934cf 100644
--- a/apps/meteor/server/ufs/ufs-store.ts
+++ b/apps/meteor/server/ufs/ufs-store.ts
@@ -229,9 +229,11 @@ export class Store {
 		};
 	}
 
-	async removeById(fileId: string, options?: { session?: ClientSession }) {
+	async removeById(fileId: string, options?: { session?: ClientSession }, isDeleted = false): Promise<void> {
 		// Delete the physical file in the store
-		await this.delete(fileId);
+		if (!isDeleted) {
+			await this.delete(fileId);
+		}
 
 		const tmpFile = UploadFS.getTempFilePath(fileId);
 


Reviews:
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-05T17:14:21Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-05T17:14:24Z
user: login: kody-ai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-05T17:14:27Z
user: login: cardoso
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-08T16:40:50Z
user: login: MarcosSpessatto
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T20:06:28Z

Review comments:
diff_hunk: @@ -45,11 +48,14 @@ export async function cleanRoomHistory({
 	});
 
 	for await (const document of cursor) {
-		const uploadsStore = FileUpload.getStore('Uploads');
+		const res = await deleteUploadedFiles(document);
 
-		document.files && (await Promise.all(document.files.map((file) => uploadsStore.deleteById(file._id))));
+		if (res.failed.length) {
+			continue;
+		}
 
 		fileCount++;
path: apps/meteor/app/lib/server/functions/cleanRoomHistory.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Potential Issues](https://img.shields.io/badge/Potential_Issues-B71C1C) ![high](https://img.shields.io/badge/severity_level-high-6B6B92)
```typescript
for await (const document of cursor) {
    const res = await deleteUploadedFiles(document);
    const totalFiles = document.files?.length || 0;
    
    if (totalFiles > 0 && res.failed.length === totalFiles) {
        logger.warn(`Failed to delete all files for message ${document._id}`);
        continue;
    }
    
    if (res.succeeded.length > 0 || totalFiles === 0) {
        fileCount++;
        
        if (filesOnly) {
            await Messages.updateOne(
                { _id: document._id },
                { $unset: { file: 1 }, $set: { attachments: [{ color: '#FD745E', text }] } }
            );
        }
    }
```
The `continue` statement in the loop causes the message processing to be skipped entirely if any associated file deletion fails, even if some deletions succeed. This behavior may not align with the intended handling of partial file deletion failures.

This issue appears in multiple locations:
* apps/meteor/app/lib/server/functions/cleanRoomHistory.ts: Lines 53-57
Please adjust the logic to ensure that messages are processed and counted even when only partial file deletions succeed, unless all deletions fail.


<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-05-05T17:14:21Z
author_association: NONE
diff_hunk: @@ -139,7 +139,7 @@ export class Store {
 			// Copy file data
 			await store.write(rs, copyId, (err) => {
 				if (err) {
-					void this.removeById(copyId);
+					void this.deleteById(copyId);
path: apps/meteor/server/ufs/ufs-store.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Potential Issues](https://img.shields.io/badge/Potential_Issues-B71C1C) ![high](https://img.shields.io/badge/severity_level-high-6B6B92)
```typescript
void store.deleteById(copyId);
```
The cleanup on copy failure incorrectly targets the source store instead of the destination store, potentially leaving partially created files in the target store.

This issue appears in multiple locations:
* apps/meteor/server/ufs/ufs-store.ts: Lines 142-142
Please ensure cleanup on copy failure targets the destination store by calling `store.deleteById(copyId)` instead of `this.deleteById(copyId)`.


<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-05-05T17:14:24Z
author_association: NONE
diff_hunk: @@ -52,7 +52,7 @@ export async function ufsComplete(fileId: string, storeName: string, options?: {
 			// Clean upload if error occurs
 			rs.on('error', (err) => {
 				console.error(err);
-				void store.removeById(fileId, { session: options?.session });
+				void store.deleteById(fileId, { session: options?.session });
path: apps/meteor/server/ufs/ufs-methods.ts
user: login: kody-ai[bot]
body: ![kody code-review](https://img.shields.io/badge/kody-code--review-312B4B?labelColor=C9BBF2) ![Error Handling](https://img.shields.io/badge/Error_Handling-FF6D00) ![high](https://img.shields.io/badge/severity_level-high-6B6B92)
```typescript
try {
  await store.deleteById(fileId, { session: options?.session });
} catch (deleteErr) {
  console.error('Failed to delete file:', deleteErr);
}
```
The `deleteById` operation lacks error handling, which could lead to incomplete cleanup in case of failures.

This issue appears in multiple locations:
* apps/meteor/server/ufs/ufs-methods.ts: Lines 55-55
* apps/meteor/server/ufs/ufs-methods.ts: Lines 78-78
Please add error handling using try-catch blocks to ensure proper cleanup and logging of failures during the `deleteById` operation.


<sub>Talk to Kody by mentioning @kody</sub>


<sub>Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.</sub>

<!-- kody-codereview -->&#8203;
&#8203;
created_at: 2025-05-05T17:14:27Z
author_association: NONE
diff_hunk: @@ -0,0 +1,192 @@
+/* eslint-disable react-hooks/rules-of-hooks */
+import { Users } from './fixtures/userStates';
+import { HomeChannel } from './page-objects';
+import { createTargetChannel } from './utils';
+import { test as baseTest, expect } from './utils/test';
+
+const FILE_SYSTEM_PATHS = {
+	STABLE: '/tmp/rc-test-ufs-local-0',
+	CHANGED: '/tmp/rc-test-ufs-local-1',
+	TEMPORARY: '/tmp/rc-test-ufs-local-2',
+};
+
+const FILE_NAMES = {
+	FILE_1: 'number1.png',
+	FILE_2: 'number2.png',
+	FILE_3: 'number3.png',
+};
+
+type SettingsFixture = {
+	settings: {
+		set: (settingId: string, value: unknown) => Promise<void>;
+	};
+};
+
+const test = baseTest.extend<SettingsFixture>({
+	settings: [
+		async ({ api }, use) => {
+			const set = async (settingId: string, value: unknown) => {
+				const response = await api.post(`/settings/${settingId}`, { value });
+				expect(response.status()).toBe(200);
+			};
+			await use({ set });
+		},
+		{},
+	],
+});
+
+test.use({ storageState: Users.admin.state });
+
+test.describe('prune-messages', () => {
+	let poHomeChannel: HomeChannel;
+	let targetChannel: string;
+
+	test.beforeAll('create target channel', async ({ api }) => {
+		targetChannel = await createTargetChannel(api, { members: [Users.admin.data.username] });
+	});
+
+	test.afterAll('delete target channel', async ({ api }) => {
+		expect((await api.post('/channels.delete', { roomName: targetChannel })).status()).toBe(200);
+	});
+
+	test.beforeEach('open chat', async ({ page }) => {
+		poHomeChannel = new HomeChannel(page);
+		await page.goto(`/channel/${targetChannel}/clean-history`);
+	});
+
+	test.describe('with attached files in FileSystem', () => {
+		test.beforeAll('set storage type to FileSystem', async ({ settings }) => {
+			await settings.set('FileUpload_Storage_Type', 'FileSystem');
+		});
+
+		test.afterAll('reset storage type to GridFS', async ({ settings }) => {
+			await settings.set('FileUpload_Storage_Type', 'GridFS');
+		});
+
+		test('should prune with stable path', async ({ page, settings }) => {
+			await settings.set('FileUpload_FileSystemPath', FILE_SYSTEM_PATHS.STABLE);
+			await sendFileMessage(poHomeChannel, FILE_NAMES.FILE_1);
+
+			let downloadUrl: string;
+
+			await test.step('download succeeds', async () => {
+				const downloadPromise = page.waitForEvent('download', (d) => d.suggestedFilename() === FILE_NAMES.FILE_1);
+				await poHomeChannel.content.lastUserMessage.getByRole('link', { name: 'Download' }).click();
+				const download = await downloadPromise;
+				expect(await download.failure()).toBeNull();
+				downloadUrl = download.url();
+			});
+
+			await pruneMessages(poHomeChannel, { filesOnly: true });
+
+			await test.step('download fails with not found (404)', async () => {
+				const download = await fetch(downloadUrl);
+				expect(download.status).toBe(404);
+			});
+
+			await pruneMessages(poHomeChannel, { filesOnly: false });
+		});
+
+		test('should prune after changing path', async ({ settings }) => {
+			await settings.set('FileUpload_FileSystemPath', FILE_SYSTEM_PATHS.CHANGED);
+			await sendFileMessage(poHomeChannel, FILE_NAMES.FILE_2);
+
+			let downloadUrl: string;
+			await test.step('download succeeds', async () => {
+				const downloadPromise = poHomeChannel.page.waitForEvent('download', (d) => d.suggestedFilename() === FILE_NAMES.FILE_2);
+				await poHomeChannel.content.lastUserMessage.getByRole('link', { name: 'Download' }).click();
+				const download = await downloadPromise;
+				expect(await download.failure()).toBeNull();
+				downloadUrl = download.url();
+			});
+
+			await settings.set('FileUpload_FileSystemPath', FILE_SYSTEM_PATHS.TEMPORARY);
+			await pruneMessages(poHomeChannel, { filesOnly: true });
+
+			await test.step('download fails with status 403 (forbidden)', async () => {
+				const download = await fetch(downloadUrl);
+				expect(download.status).toBe(403);
+			});
+
+			await settings.set('FileUpload_FileSystemPath', FILE_SYSTEM_PATHS.CHANGED);
+
+			await test.step('download succeeds', async () => {
+				const downloadPromise = poHomeChannel.page.waitForEvent('download', (d) => d.suggestedFilename() === FILE_NAMES.FILE_2);
+				await poHomeChannel.content.lastUserMessage.getByRole('link', { name: 'Download' }).click();
+				const download = await downloadPromise;
+				expect(await download.failure()).toBeNull();
+				downloadUrl = download.url();
+			});
+
+			await pruneMessages(poHomeChannel, { filesOnly: true });
+
+			await test.step('download fails with status 404 (not found)', async () => {
+				const download = await fetch(downloadUrl);
+				expect(download.status).toBe(404);
+			});
+
+			await pruneMessages(poHomeChannel, { filesOnly: false });
+		});
+
+		// FIXME: This will fail in the afterAll step when trying to delete the channel
+		test.fail('delete channel with dangling files', async ({ settings }) => {
+			await settings.set('FileUpload_FileSystemPath', FILE_SYSTEM_PATHS.CHANGED);
+			await sendFileMessage(poHomeChannel, FILE_NAMES.FILE_3);
+			await settings.set('FileUpload_FileSystemPath', FILE_SYSTEM_PATHS.TEMPORARY);
+		});
+
+		// FIXME: This will fail because the chat.delete API will return 400 (ENOENT)
+		test.fail('delete message with dangling files', async ({ settings }) => {
+			await settings.set('FileUpload_FileSystemPath', FILE_SYSTEM_PATHS.CHANGED);
+			await sendFileMessage(poHomeChannel, FILE_NAMES.FILE_3);
+			await settings.set('FileUpload_FileSystemPath', FILE_SYSTEM_PATHS.TEMPORARY);
+			await poHomeChannel.page.getByLabel('Close').click();
+			await poHomeChannel.content.openLastMessageMenu();
+			await poHomeChannel.page.getByRole('menuitem', { name: 'Delete' }).click();
+			const modal = poHomeChannel.page.getByLabel('Are you sure?');
+			await modal.getByRole('button', { name: 'Yes, delete' }).click();
+			await expect(modal).not.toBeVisible();
+			await expect(poHomeChannel.content.lastUserMessage).not.toBeVisible();
+		});
+	});
path: apps/meteor/tests/e2e/prune-messages.spec.ts
user: login: cardoso
body: If we delete the message when `fs.unlink` fails for any reason, then we lose track of that file and won't be able to delete it properly.

Ideally, the message gets deleted/hidden, but the reference to the file remains in the database so pruning is still attempted later.

I forced `ENOENT` in this test suite by changing `FileUpload_FileSystemPath` as just an easy way to reproduce. There are many possible errors:

https://man7.org/linux/man-pages/man2/unlink.2.html#ERRORS
created_at: 2025-05-08T16:40:50Z
author_association: MEMBER

PR comments:
user: login: dionisio-bot[bot]
created_at: 2025-04-29T18:52:32Z
body: Looks like this PR is ready to merge! 🎉
If you have any trouble, please check the [PR guidelines](https://handbook.rocket.chat/departments-and-operations/research-and-development/engineering/development/pr-general-instructions-and-handling)
author_association: CONTRIBUTOR
user: login: changeset-bot[bot]
created_at: 2025-04-29T18:52:35Z
body: ###  🦋  Changeset detected

Latest commit: 41b1d9cc90aa7a184750d99b5d881af618bd8b8e

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 36 packages</summary>

  | Name                                | Type  |
| ----------------------------------- | ----- |
| @rocket.chat/meteor                 | Patch |
| @rocket.chat/core-typings           | Patch |
| @rocket.chat/rest-typings           | Patch |
| @rocket.chat/uikit-playground       | Patch |
| @rocket.chat/api-client             | Patch |
| @rocket.chat/apps                   | Patch |
| @rocket.chat/core-services          | Patch |
| @rocket.chat/cron                   | Patch |
| @rocket.chat/ddp-client             | Patch |
| @rocket.chat/freeswitch             | Patch |
| @rocket.chat/fuselage-ui-kit        | Patch |
| @rocket.chat/gazzodown              | Patch |
| @rocket.chat/livechat               | Patch |
| @rocket.chat/model-typings          | Patch |
| @rocket.chat/ui-contexts            | Patch |
| @rocket.chat/account-service        | Patch |
| @rocket.chat/authorization-service  | Patch |
| @rocket.chat/ddp-streamer           | Patch |
| @rocket.chat/omnichannel-transcript | Patch |
| @rocket.chat/presence-service       | Patch |
| @rocket.chat/queue-worker           | Patch |
| @rocket.chat/stream-hub-service     | Patch |
| @rocket.chat/license                | Patch |
| @rocket.chat/omnichannel-services   | Patch |
| @rocket.chat/pdf-worker             | Patch |
| @rocket.chat/presence               | Patch |
| rocketchat-services                 | Patch |
| @rocket.chat/models                 | Patch |
| @rocket.chat/network-broker         | Patch |
| @rocket.chat/mock-providers         | Patch |
| @rocket.chat/ui-avatar              | Patch |
| @rocket.chat/ui-client              | Patch |
| @rocket.chat/ui-video-conf          | Patch |
| @rocket.chat/ui-voip                | Patch |
| @rocket.chat/web-ui-registration    | Patch |
| @rocket.chat/instance-status        | Patch |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/RocketChat/Rocket.Chat/new/SUP-745-fix-pruning-ufs-local?filename=.changeset/dull-peaches-collect.md&value=---%0A%22%40rocket.chat%2Fmeteor%22%3A%20patch%0A---%0A%0Afix%3A%20file%20deletion%20called%20twice%20and%20ignoring%20errors%20with%20FileSystem%20storage%20type%0A)


author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-04-29T18:52:39Z
body: # Kody Review Complete
**Great news!** 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: codecov[bot]
created_at: 2025-04-29T18:58:11Z
body: ## [Codecov](https://app.codecov.io/gh/RocketChat/Rocket.Chat/pull/35905?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat) Report
Attention: Patch coverage is `91.11111%` with `4 lines` in your changes missing coverage. Please review.
> Project coverage is 64.71%. Comparing base [(`aadc795`)](https://app.codecov.io/gh/RocketChat/Rocket.Chat/commit/aadc7956bdeb281007ef06f158088640a44171df?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat) to head [(`41b1d9c`)](https://app.codecov.io/gh/RocketChat/Rocket.Chat/commit/41b1d9cc90aa7a184750d99b5d881af618bd8b8e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat).
> Report is 1 commits behind head on develop.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/RocketChat/Rocket.Chat/pull/35905/graphs/tree.svg?width=650&height=150&src=pr&token=lEAH159Fca&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat)](https://app.codecov.io/gh/RocketChat/Rocket.Chat/pull/35905?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat)

```diff
@@             Coverage Diff             @@
##           develop   #35905      +/-   ##
===========================================
- Coverage    64.73%   64.71%   -0.02%     
===========================================
  Files         3106     3107       +1     
  Lines        92877    93094     +217     
  Branches     17675    17775     +100     
===========================================
+ Hits         60127    60250     +123     
- Misses       29964    30056      +92     
- Partials      2786     2788       +2     
```

| [Flag](https://app.codecov.io/gh/RocketChat/Rocket.Chat/pull/35905/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat) | Coverage Δ | |
|---|---|---|
| [e2e](https://app.codecov.io/gh/RocketChat/Rocket.Chat/pull/35905/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat) | `58.38% <ø> (-0.01%)` | :arrow_down: |
| [unit](https://app.codecov.io/gh/RocketChat/Rocket.Chat/pull/35905/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat) | `71.49% <91.11%> (-0.18%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=RocketChat#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-29T18:58:18Z
body: [PR Preview Action](https://github.com/rossjrw/pr-preview-action) v1.6.1
:---:
| <p></p> :rocket: View preview at <br> https://RocketChat.github.io/Rocket.Chat/pr-preview/pr-35905/ <br><br>
| <h6>Built to branch [`gh-pages`](https://github.com/RocketChat/Rocket.Chat/tree/gh-pages) at 2025-05-19 23:16 UTC. <br> Preview will be ready when the [GitHub Pages deployment](https://github.com/RocketChat/Rocket.Chat/deployments) is complete. <br><br> </h6>
<!-- Sticky Pull Request Commentpr-preview -->
author_association: CONTRIBUTOR
user: login: kody-ai[bot]
created_at: 2025-04-29T20:05:16Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-04-30T15:16:43Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-02T14:45:31Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-02T16:38:03Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-02T17:45:25Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-02T17:49:56Z
body: # Kody Review Complete
**Great news!** 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-05T17:12:49Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-05T20:55:40Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-06T17:49:29Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-06T17:52:37Z
body: # Kody Review Complete
**Great news!** 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-06T21:21:44Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-06T21:21:56Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-07T16:22:27Z
body: # Kody Review Complete
**Great news!** 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-07T18:31:53Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-07T23:29:31Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-08T00:22:23Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-08T16:42:21Z
body: # Kody Review Complete
**Great news!** 🎉
No issues were found that match your current review configurations.

Keep up the excellent work! 🚀

<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: kody-ai[bot]
created_at: 2025-05-09T14:09:48Z
body: ## Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.



<details>
<summary>Kody Guide: Usage and Configuration</summary>

<details>
<summary>Interacting with Kody</summary>

- **Request a Review:** Ask Kody to review your PR manually by adding a comment with the `@kody start-review` command at the root of your PR.

- **Provide Feedback:** Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

</details>

<details>
<summary>Current Kody Configuration</summary>

<details>
<summary>Review Options</summary>

The following review options are enabled or disabled:

| Options                        | Enabled |
|-------------------------------|---------|
| **Security** | ✅ |
| **Code Style** | ✅ |
| **Kody Rules** | ✅ |
| **Refactoring** | ✅ |
| **Error Handling** | ✅ |
| **Maintainability** | ✅ |
| **Potential Issues** | ✅ |
| **Documentation And Comments** | ✅ |
| **Performance And Optimization** | ✅ |
| **Breaking Changes** | ❌ |

</details>

**[Access your configuration settings here.](https://app.kodus.io/settings/code-review/global/general)**

</details>
</details>

<!-- kody-codereview -->
&#8203;
author_association: NONE
user: login: debdutdeb
created_at: 2025-05-17T16:12:21Z
body: Does this really "fix" the issue? Or is it a better version of the old code i.e. with better logging?

I'm asking because i don't necessarily see any change that makes it actually delete things, not do I see the old code not deleting things. 

Unless stat fails both times (first actual call, second i don't know why, but regardless). If so do you know why?
author_association: MEMBER
user: login: cardoso
created_at: 2025-05-17T17:58:56Z
body: @debdutdeb there's a [root cause analysis](https://rocketchat.atlassian.net/browse/SUP-745?focusedCommentId=50998) in the ticket and 3 other related tickets which were part of the issue and were merged (especially https://github.com/RocketChat/Rocket.Chat/pull/35797 which clears the files array).

About the improvements here: the old code would call delete twice, and there are possible race conditions between stat and unlink. There's no reason to call stat since unlink will perform the same pre-conditions and throw.

Indeed there's no way to fully guarantee deletion, but now we'll see real errors if something goes wrong (except for the file not existing, which is expected in some cases like changing `FileUpload_FileSystemPath` apart from the double call fixed here).

Maybe this PR can be renamed.
author_association: MEMBER
user: login: MarcosSpessatto
created_at: 2025-05-19T12:38:42Z
body: > @debdutdeb there's a [root cause analysis](https://rocketchat.atlassian.net/browse/SUP-745?focusedCommentId=50998) in the ticket and 3 other related tickets which were part of the issue and were merged (especially #35797 which clears the files array).
> 
> About the improvements here: the old code would call delete twice, and there are possible race conditions between stat and unlink. There's no reason to call stat since unlink will perform the same pre-conditions and throw.
> 
> Indeed there's no way to fully guarantee deletion, but now we'll see real errors if something goes wrong (except for the file not existing, which is expected in some cases like changing `FileUpload_FileSystemPath` apart from the double call fixed here).
> 
> Maybe this PR can be renamed.

I think it makes sense to rename it @cardoso, maybe a chore instead?
author_association: CONTRIBUTOR
user: login: cardoso
created_at: 2025-05-19T13:10:36Z
body: @MarcosSpessatto I updated the name already to match what's being done:

` fix: pruning does not get rid of physical files` -> `fix: file deletion called twice and ignoring errors with FileSystem storage type`

I think it's better to keep it as a fix, but I can change to chore if you prefer.


author_association: MEMBER