RocketChat/Rocket.Chat · error · Error
The environmental variable
Error message
The environmental variable "${envVarName}" is not readable. What it means
Catch-all 404 for FileSystem:Uploads.get: fsp.stat threw (usually ENOENT — the Uploads document exists in Mongo but the file is gone from disk), or a later step (Range parsing via getFileRange, stream creation) threw. The original error is swallowed; only a bodyless 404 reaches the client, which makes root-causing harder.
Solutions
- Confirm FileUpload_FileSystemPath still points at the volume that actually holds the uploads
- Restore the missing files from backup, or clean up the dangling Uploads records if the loss is accepted
- Add temporary logging in the catch to surface the real error (ENOENT vs EACCES vs range parsing) before deciding
- Check the Range header of failing clients if only some requests 404
Example fix
// before
} catch (e) {
res.writeHead(404);
res.end();
}
// after
} catch (e) {
SystemLogger.error({ msg: 'file serve failed', fileId: file._id, code: e.code, message: e.message });
res.writeHead(404);
res.end();
} Defensive patterns
Strategy: try-catch
Validate before calling
const ok = await fsp.stat(await store.getFilePath(file._id, file)).then((s) => s.isFile()).catch(() => false);
if (!ok) { /* record exists but bytes missing: quarantine/report instead of 404 */ } Try / catch
Wrap the whole serve step in try/catch; inspect (e as NodeJS.ErrnoException).code — ENOENT means missing file (restore/re-upload), EACCES means permissions (fix ownership), anything else points at range/stream logic; log e with the fileId before responding 404.
Prevention
- Log the underlying error in every catch that converts to 404 — a silent 404 hides ENOENT/EACCES/range bugs
- Back up the filesystem uploads directory with the database
- Alert on 404 spikes for /file-upload/* after deployments or volume changes
When it happens
Trigger: Upload record exists but the file was deleted from the disk store: FileUpload_FileSystemPath changed or the volume is not mounted; disk cleanup jobs pruned uploads; DB restored without the files directory. Also fires when getFileRange throws on a malformed Range header or the read stream cannot be created (EACCES).
Common situations: Docker/K8s volume mounts missing after restart; migrations between storage types; DB-only backups; aggressive disk cleanup; permission changes on the uploads directory.
Related errors
- auth option should be of the form "username:password"
- Error sending file to apps
- File upload is unauthorized to connect on Webdav, please…
- Not found
- Transfer to entity with id
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/cbf30648fc30e049.
Report an issue: GitHub.
Appendix: source
Thrown at apps/meteor/app/apps/server/bridges/environmental.ts:16
import type { IAppServerOrchestrator } from '@rocket.chat/apps';
import { EnvironmentalVariableBridge } from '@rocket.chat/apps/dist/server/bridges/EnvironmentalVariableBridge';
export class AppEnvironmentalVariableBridge extends EnvironmentalVariableBridge {
allowed: Array<string>;
constructor(private readonly orch: IAppServerOrchestrator) {
super();
this.allowed = ['NODE_ENV', 'ROOT_URL', 'INSTANCE_IP'];
}
protected async getValueByName(envVarName: string, appId: string): Promise<string | undefined> {
this.orch.debugLog(`The App ${appId} is getting the environmental variable value ${envVarName}.`);
if (!(await this.isReadable(envVarName, appId))) {
throw new Error(`The environmental variable "${envVarName}" is not readable.`);
}
return process.env[envVarName];
}
protected async isReadable(envVarName: string, appId: string): Promise<boolean> {
this.orch.debugLog(`The App ${appId} is checking if the environmental variable is readable ${envVarName}.`);
return this.allowed.includes(envVarName.toUpperCase()) || this.isAppsOwnVariable(envVarName, appId);
}
protected isAppsOwnVariable(envVarName: string, appId: string): boolean {
/**
* Replace the letter `-` with `_` since environment variable name doesn't support it
*/
const appVariablePrefix = `RC_APPS_${appId.toUpperCase().replace(/-/g, '_')}`;
return envVarName.toUpperCase().startsWith(appVariablePrefix);
}View on GitHub (pinned to b2c16d5842)