{"record":{"id":"ca64c69f9fec3998","repo":"remix-run/react-router","slug":"error-generating-session","errorCode":null,"errorMessage":"Error generating session","messagePattern":"Error generating session","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/react-router-node/sessions/fileStorage.ts","lineNumber":51,"sourceCode":"  dir,\n}: FileSessionStorageOptions): SessionStorage<Data, FlashData> {\n  return createSessionStorage({\n    cookie,\n    async createData(data, expires) {\n      let content = JSON.stringify({ data, expires });\n\n      while (true) {\n        let randomBytes = crypto.getRandomValues(new Uint8Array(8));\n        // This storage manages an id space of 2^64 ids, which is far greater\n        // than the maximum number of files allowed on an NTFS or ext4 volume\n        // (2^32). However, the larger id space should help to avoid collisions\n        // with existing ids when creating new sessions, which speeds things up.\n        let id = Buffer.from(randomBytes).toString(\"hex\");\n\n        try {\n          let file = getFile(dir, id);\n          if (!file) {\n            throw new Error(\"Error generating session\");\n          }\n          await fsp.mkdir(path.dirname(file), { recursive: true });\n          await fsp.writeFile(file, content, { encoding: \"utf-8\", flag: \"wx\" });\n          return id;\n        } catch (error: any) {\n          if (error.code !== \"EEXIST\") throw error;\n        }\n      }\n    },\n    async readData(id) {\n      try {\n        let file = getFile(dir, id);\n        if (!file) {\n          return null;\n        }\n        let content = JSON.parse(await fsp.readFile(file, \"utf-8\"));\n        let data = content.data;\n        let expires =","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/remix-run/react-router/blob/1fd704a7dabcbe3ae09d7387b460e6acaba30ec1/packages/react-router-node/sessions/fileStorage.ts#L33-L69","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// test setup — before (breaks id generation)\nglobalThis.crypto = { getRandomValues: () => new Uint8Array([0xff]) }; // wrong length\n// after\nconst { webcrypto } = require('node:crypto');\nglobalThis.crypto = webcrypto; // real implementation fills the requested 8 bytes","handlingStrategy":"try-catch","validationCode":"// sanity check the crypto path used by fileStorage\nconst bytes = new Uint8Array(8); globalThis.crypto.getRandomValues(bytes);\nconst id = Buffer.from(bytes).toString('hex');\nif (!/^[0-9a-f]{16}$/i.test(id)) throw new Error('crypto.getRandomValues produced an unexpected id shape');","typeGuard":"function isValidFileSessionId(id: string): boolean {\n  return /^[0-9a-f]{16}$/i.test(id);\n}","tryCatchPattern":"try { await sessionStorage.createData(content); }\ncatch (e) {\n  if (e instanceof Error && e.message === 'Error generating session') {\n    // check globalThis.crypto / Buffer polyfills in the runtime\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["sessions","file-storage","crypto","defensive"],"backgroundTag":null,"analyzedSha":"1fd704a7dabcbe3ae09d7387b460e6acaba30ec1","analyzedAt":"2026-08-12T13:54:57.804Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}