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

  1. Confirm FileUpload_FileSystemPath still points at the volume that actually holds the uploads
  2. Restore the missing files from backup, or clean up the dangling Uploads records if the loss is accepted
  3. Add temporary logging in the catch to surface the real error (ENOENT vs EACCES vs range parsing) before deciding
  4. 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

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


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)