RocketChat/Rocket.Chat · error · Error

Error sending file to apps

Error message

Error sending file to apps

What it means

On the IPreFileUpload event, `AppListenerBridge.uploadEvent` (listeners.ts:252-271) stages the upload into a temp file. When `content` is a string it treats it as a path and creates a symlink `fs.symlink(content, tmpfile, 'file')`. If that OS call fails it logs the cause and re-throws a generic 'Error sending file to apps' with the original error attached as `cause` (listeners.ts:259-263). The upload event never reaches App listeners.

Source

Thrown at apps/meteor/app/apps/server/bridges/listeners.ts:262

				return this.defaultEvent(args);
		}
	}

	async defaultEvent(args: HandleDefaultEvent): Promise<unknown> {
		return this.orch.getManager().getListenerManager().executeListener(args.event, args.payload[0]);
	}

	async uploadEvent(args: HandleFileUploadEvent): Promise<void> {
		const [{ file, content }] = args.payload;

		const tmpfile = path.join(this.orch.getManager().getTempFilePath(), crypto.randomUUID());

		if (typeof content === 'string') {
			// If content is a string, we assume it's a path and create a symlink to avoid file duplication
			await fs.promises.symlink(content, tmpfile, 'file').catch((err) => {
				this.orch.getRocketChatLogger().error({ msg: `AppListenerBridge: Could not create symlink at ${tmpfile}`, err });

				throw new Error('Error sending file to apps', { cause: err });
			});
		} else {
			// Otherwise, we write the buffer content to a temporary file
			await fs.promises.writeFile(tmpfile, content).catch((err) => {
				this.orch.getRocketChatLogger().error({ msg: `AppListenerBridge: Could not write temporary file at ${tmpfile}`, err });

				throw new Error('Error sending file to apps', { cause: err });
			});
		}

		try {
			const uploadDetails = {
				name: file.name || '',
				size: file.size || 0,
				type: file.type || '',
				rid: file.rid || '',
				userId: file.userId || '',
				visitorToken: file.token,

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Inspect error.cause for the real errno (ENOENT/EACCES/EXDEV).
  2. Ensure the temp path (getTempFilePath()) is writable and on the same device as the source.
  3. Verify the source path still exists when content is a string.
  4. Fix filesystem permissions or remount tmp as writable.
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await appListenerBridge.uploadEvent(args);
} catch (e) {
  const cause = (e as Error & { cause?: NodeJS.ErrnoException }).cause;
  logger.error({ msg: 'File staging (symlink) failed', code: cause?.code, err: e });
  // surface a user-facing upload error; do not blind-retry on ENOENT/EACCES
}

Prevention

When it happens

Trigger: Source path missing (ENOENT), permission denied (EACCES) on source or temp dir, cross-device symlink not permitted (EXDEV), or an invalid path.

Common situations: The upload's content path was deleted/moved before staging; temp dir not writable; container with a read-only filesystem; SELinux denying symlink creation.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/ea5a9b0ee93d0e98. Report an issue: GitHub.