remix-run/react-router · error · Error
Error generating session
Error message
Error generating session
What it means
Thrown by createFileSessionStorage's createData when getFile(dir, id) returns null. getFile validates the id against /^[0-9a-f]{16}$/i (16 hex chars). Because the id is derived from 8 random bytes hex-encoded (always 16 lowercase hex chars), this guard is effectively a defensive invariant — if it fires, something tampered with the id or getFile's regex, or the environment's Buffer/crypto produced an unexpected value. The EEXIST case (file already exists) is retried in the while-loop, not surfaced here.
Source
Thrown at packages/react-router-node/sessions/fileStorage.ts:51
dir,
}: FileSessionStorageOptions): SessionStorage<Data, FlashData> {
return createSessionStorage({
cookie,
async createData(data, expires) {
let content = JSON.stringify({ data, expires });
while (true) {
let randomBytes = crypto.getRandomValues(new Uint8Array(8));
// This storage manages an id space of 2^64 ids, which is far greater
// than the maximum number of files allowed on an NTFS or ext4 volume
// (2^32). However, the larger id space should help to avoid collisions
// with existing ids when creating new sessions, which speeds things up.
let id = Buffer.from(randomBytes).toString("hex");
try {
let file = getFile(dir, id);
if (!file) {
throw new Error("Error generating session");
}
await fsp.mkdir(path.dirname(file), { recursive: true });
await fsp.writeFile(file, content, { encoding: "utf-8", flag: "wx" });
return id;
} catch (error: any) {
if (error.code !== "EEXIST") throw error;
}
}
},
async readData(id) {
try {
let file = getFile(dir, id);
if (!file) {
return null;
}
let content = JSON.parse(await fsp.readFile(file, "utf-8"));
let data = content.data;
let expires =View on GitHub (pinned to 1fd704a7da)
Solutions
- Confirm the Node runtime supports globalThis.crypto.getRandomValues (Node 19+ has it global; older needs the WebCrypto polyfill).
- If stubbing crypto in tests, ensure getRandomValues fills exactly 8 bytes with random values.
- Avoid monkeypatching Buffer.prototype.toString or the fileStorage module.
- Upgrade @react-router/node to the latest patch in case the id-generation contract changed.
Example fix
// test setup — before (breaks id generation)
globalThis.crypto = { getRandomValues: () => new Uint8Array([0xff]) }; // wrong length
// after
const { webcrypto } = require('node:crypto');
globalThis.crypto = webcrypto; // real implementation fills the requested 8 bytes Defensive patterns
Strategy: try-catch
Validate before calling
// sanity check the crypto path used by fileStorage
const bytes = new Uint8Array(8); globalThis.crypto.getRandomValues(bytes);
const id = Buffer.from(bytes).toString('hex');
if (!/^[0-9a-f]{16}$/i.test(id)) throw new Error('crypto.getRandomValues produced an unexpected id shape'); Type guard
function isValidFileSessionId(id: string): boolean {
return /^[0-9a-f]{16}$/i.test(id);
} Try / catch
try { await sessionStorage.createData(content); }
catch (e) {
if (e instanceof Error && e.message === 'Error generating session') {
// check globalThis.crypto / Buffer polyfills in the runtime
}
throw e;
} Prevention
- Run on Node 19+ (global crypto) or polyfill WebCrypto correctly.
- Don't stub crypto.getRandomValues in tests without filling 8 bytes.
- Avoid monkeypatching Buffer.prototype.toString.
When it happens
Trigger: The generated id fails the /^[0-9a-f]{16}$/i test inside getFile. Reachable only if crypto.getRandomValues returns something other than 8 bytes, Buffer.from(...).toString('hex') yields non-hex or wrong length, or getFile's regex was monkeypatched. In normal Node this is essentially unreachable.
Common situations: A test environment that stubs crypto.getRandomValues incorrectly. A polyfill that changes Buffer.from/toString behavior. Corruption of the fileStorage module. Not a typical production failure.
Related errors
AI-assisted analysis of remix-run/react-router@1fd704a7da (2026-08-12).
Data as JSON: /api/errors/ca64c69f9fec3998.
Report an issue: GitHub.