{"record":{"id":"ea5a9b0ee93d0e98","repo":"RocketChat/Rocket.Chat","slug":"error-sending-file-to-apps","errorCode":null,"errorMessage":"Error sending file to apps","messagePattern":"Error sending file to apps","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/app/apps/server/bridges/listeners.ts","lineNumber":262,"sourceCode":"\t\t\t\treturn this.defaultEvent(args);\n\t\t}\n\t}\n\n\tasync defaultEvent(args: HandleDefaultEvent): Promise<unknown> {\n\t\treturn this.orch.getManager().getListenerManager().executeListener(args.event, args.payload[0]);\n\t}\n\n\tasync uploadEvent(args: HandleFileUploadEvent): Promise<void> {\n\t\tconst [{ file, content }] = args.payload;\n\n\t\tconst tmpfile = path.join(this.orch.getManager().getTempFilePath(), crypto.randomUUID());\n\n\t\tif (typeof content === 'string') {\n\t\t\t// If content is a string, we assume it's a path and create a symlink to avoid file duplication\n\t\t\tawait fs.promises.symlink(content, tmpfile, 'file').catch((err) => {\n\t\t\t\tthis.orch.getRocketChatLogger().error({ msg: `AppListenerBridge: Could not create symlink at ${tmpfile}`, err });\n\n\t\t\t\tthrow new Error('Error sending file to apps', { cause: err });\n\t\t\t});\n\t\t} else {\n\t\t\t// Otherwise, we write the buffer content to a temporary file\n\t\t\tawait fs.promises.writeFile(tmpfile, content).catch((err) => {\n\t\t\t\tthis.orch.getRocketChatLogger().error({ msg: `AppListenerBridge: Could not write temporary file at ${tmpfile}`, err });\n\n\t\t\t\tthrow new Error('Error sending file to apps', { cause: err });\n\t\t\t});\n\t\t}\n\n\t\ttry {\n\t\t\tconst uploadDetails = {\n\t\t\t\tname: file.name || '',\n\t\t\t\tsize: file.size || 0,\n\t\t\t\ttype: file.type || '',\n\t\t\t\trid: file.rid || '',\n\t\t\t\tuserId: file.userId || '',\n\t\t\t\tvisitorToken: file.token,","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/app/apps/server/bridges/listeners.ts#L244-L280","documentation":"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.","triggerScenarios":"Source path missing (ENOENT), permission denied (EACCES) on source or temp dir, cross-device symlink not permitted (EXDEV), or an invalid path.","commonSituations":"The upload's content path was deleted/moved before staging; temp dir not writable; container with a read-only filesystem; SELinux denying symlink creation.","solutions":["Inspect error.cause for the real errno (ENOENT/EACCES/EXDEV).","Ensure the temp path (getTempFilePath()) is writable and on the same device as the source.","Verify the source path still exists when content is a string.","Fix filesystem permissions or remount tmp as writable."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await appListenerBridge.uploadEvent(args);\n} catch (e) {\n  const cause = (e as Error & { cause?: NodeJS.ErrnoException }).cause;\n  logger.error({ msg: 'File staging (symlink) failed', code: cause?.code, err: e });\n  // surface a user-facing upload error; do not blind-retry on ENOENT/EACCES\n}","preventionTips":["Monitor disk space and temp-dir writability on the Rocket.Chat host.","Ensure the uploads directory and temp path share a filesystem to avoid EXDEV on symlinks.","Confirm source file paths still exist before staging when content is a path string."],"tags":["apps-engine","listeners","filesystem","upload"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}