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
- 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.
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
- 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.
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
- Room with id ${transferData.room} not found
- Transfer to entity with id ${transferData.to} not found
- Invalid Api parameter provided, it must be a valid IApi obje
- Invalid command parameter provided, must be a string.
- The command is not currently disabled: "${cmd}"
AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12).
Data as JSON: /api/errors/ea5a9b0ee93d0e98.
Report an issue: GitHub.