{"record":{"id":"7168ada85389410d","repo":"vercel/hyper","slug":"error-reading-configuration-module-exports-not","errorCode":null,"errorMessage":"Error reading configuration: `module.exports` not set","messagePattern":"Error reading configuration: `module\\.exports` not set","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/config/init.ts","lineNumber":13,"sourceCode":"import vm from 'vm';\n\nimport merge from 'lodash/merge';\n\nimport type {parsedConfig, rawConfig, configOptions} from '../../typings/config';\nimport notify from '../notify';\nimport mapKeys from '../utils/map-keys';\n\nconst _extract = (script?: vm.Script): Record<string, any> => {\n  const module: Record<string, any> = {};\n  script?.runInNewContext({module}, {displayErrors: true});\n  if (!module.exports) {\n    throw new Error('Error reading configuration: `module.exports` not set');\n  }\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n  return module.exports;\n};\n\nconst _syntaxValidation = (cfg: string) => {\n  try {\n    return new vm.Script(cfg, {filename: '.hyper.js'});\n  } catch (_err) {\n    const err = _err as {name: string};\n    notify(`Error loading config: ${err.name}`, JSON.stringify(err), {error: err});\n  }\n};\n\nconst _extractDefault = (cfg: string) => {\n  return _extract(_syntaxValidation(cfg));\n};\n","sourceCodeStart":1,"sourceCodeEnd":31,"githubUrl":"https://github.com/vercel/hyper/blob/da0c401d7f9e197b1fa6f29854adffa43d3a3287/app/config/init.ts#L1-L31","documentation":"Thrown by Hyper's config loader (_extract in app/config/init.ts) when it runs the user's `~/.hyper.js` as a vm.Script inside a sandbox exposing a fresh `module` object, and after execution `module.exports` is still falsy. The file is expected to be CommonJS that assigns `module.exports = {config: {...}, keymaps: ..., plugins: [...]}`. If the script is valid syntactically (it passed _syntaxValidation) but never performs that assignment, the loader cannot read a config and aborts. Note: a syntax error does NOT trigger this — _syntaxValidation catches those, notifies, and returns undefined, which then makes _extract's optional-chained `script?.runInNewContext` a no-op, leaving module.exports undefined, so the error can also follow an earlier syntax failure.","triggerScenarios":"Loading a `~/.hyper.js` that (a) is empty or whitespace-only, (b) defines `config = {...}` as a free variable instead of assigning to `module.exports`, (c) uses ES-module syntax (`export default {...}`) which is valid as a vm.Script token sequence but does not set module.exports, (d) only contains comments, or (e) had a syntax error caught by _syntaxValidation (returns undefined) so the script never runs and module.exports stays undefined.","commonSituations":"User hand-edited `.hyper.js` and deleted the `module.exports =` portion; user followed an ES-modules tutorial and wrote `export default`; a config-migration or theme-install snippet wrote bare object literals; editor saved a partial file during crash; new install where the file was created empty by a setup step; syntax error earlier in the file (caught silently by _syntaxValidation, then this error surfaces on the subsequent extract).","solutions":["Open `~/.hyper.js` and ensure it contains exactly one CommonJS export: `module.exports = { config: {}, keymaps: {}, plugins: [], localPlugins: [] };`.","If the file is empty or unrecognizable, replace its contents with the default from app/config/config-default.json (or run `hyper` to regenerate the default).","Remove any `export default` / `import` ESM syntax — Hyper's config is CommonJS evaluated in a vm sandbox, not bundled.","Check for an earlier syntax error: a desktop notification 'Error loading config: SyntaxError' precedes this throw; fix that first because _syntaxValidation returning undefined is what makes _extract skip execution.","Validate the file with `node -c ~/.hyper.js` (syntax check) and `node -e \"const m={}; require('vm').runInNewContext(require('fs').readFileSync(process.env.HOME+'/.hyper.js','utf8'),{module:m}); console.log(typeof m.exports)\"` to confirm it logs 'object'."],"exampleFix":"// before — ~/.hyper.js (broken)\n// only wrote the object literal, no export\n{\n  config: { fontSize: 12 }\n}\n// -> Error reading configuration: `module.exports` not set\n\n// after — ~/.hyper.js (fixed, CommonJS export)\nmodule.exports = {\n  config: {\n    fontSize: 12,\n    fontFamily: 'Menlo, monospace',\n  },\n  plugins: [],\n  localPlugins: [],\n  keymaps: {},\n};","handlingStrategy":"try-catch","validationCode":"// Validate the user config string BEFORE running it through _extract/_extractDefault.\n// Mirror the loader's own contract: must compile as a script AND assign module.exports.\nimport vm from 'vm';\n\nfunction isAssignableConfig(cfg: string): boolean {\n  if (!cfg || !cfg.trim()) return false;\n  let script: vm.Script;\n  try {\n    script = new vm.Script(cfg, {filename: '.hyper.js'});\n  } catch {\n    return false; // syntax error — _syntaxValidation would notify and return undefined\n  }\n  const module: Record<string, any> = {};\n  try {\n    script.runInNewContext({module}, {displayErrors: false});\n  } catch {\n    return false; // runtime error during assignment\n  }\n  return module.exports != null && typeof module.exports === 'object';\n}\n\n// usage before _extractDefault\nif (!isAssignableConfig(rawUserCfgString)) {\n  notify('Configuration file is incomplete', 'Recreating ~/.hyper.js with defaults');\n  rawUserCfgString = defaultCfgString;\n}","typeGuard":"// Narrow the extracted object after a successful _extract call.\n// _extract already guarantees module.exports is truthy; this guard validates shape.\nimport type { rawConfig } from '../../typings/config';\n\nfunction isRawConfig(v: unknown): v is rawConfig {\n  if (typeof v !== 'object' || v === null) return false;\n  const r = v as Record<string, unknown>;\n  // config is optional at this layer but if present must be an object\n  if ('config' in r && (typeof r.config !== 'object' || r.config === null)) return false;\n  if ('plugins' in r && !Array.isArray(r.plugins)) return false;\n  if ('localPlugins' in r && !Array.isArray(r.localPlugins)) return false;\n  if ('keymaps' in r && (typeof r.keymaps !== 'object' || r.keymaps === null)) return false;\n  return true;\n}\n\nconst extracted = _extract(script);\nif (!isRawConfig(extracted)) {\n  throw new Error('Configuration shape invalid after extraction');\n}","tryCatchPattern":"// _extract throws synchronously. Wrap _extractDefault / _init callers so a broken\n// user config never takes down app boot — fall back to defaults and notify.\nimport {_init, _extractDefault} from './config/init';\nimport defaultRaw from './config/config-default.json';\n\nfunction safeInit(userRawString: string, defaultRawString: string) {\n  let userCfg;\n  try {\n    userCfg = _extractDefault(userRawString);\n  } catch (err) {\n    const e = err as Error;\n    if (/module\\.exports not set/i.test(e.message)) {\n      notify('Configuration unreadable', 'Using defaults until ~/.hyper.js sets module.exports');\n      userCfg = _extractDefault(defaultRawString); // guaranteed-valid default\n    } else {\n      throw e; // unknown failure — do not swallow\n    }\n  }\n  return _init(userCfg, _extractDefault(defaultRawString));\n}","preventionTips":["Always write `~/.hyper.js` as `module.exports = {...}` — never a bare object literal or `export default`.","After editing, run `node -c ~/.hyper.js` for a syntax check and `node -e \"console.log(typeof require('vm').runInNewContext(require('fs').readFileSync(process.env.HOME+'/.hyper.js','utf8'),{module:{}}).exports)\"` which should print 'object'.","Never leave `.hyper.js` empty or comments-only; if clearing it, restore from app/config/config-default.json.","When shipping a config-migration script, write atomically (temp file + rename) so a crash cannot leave a partial file without an export.","Watch for the preceding 'Error loading config: SyntaxError' desktop notification — fix that first, because _syntaxValidation returning undefined is what starves _extract of a script and produces this throw."],"tags":["config","commonjs","vm","user-config","runtime","esm"],"backgroundTag":null,"analyzedSha":"da0c401d7f9e197b1fa6f29854adffa43d3a3287","analyzedAt":"2026-08-12T19:28:14.584Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}