vercel/hyper · error · Error
Error reading configuration: `module.exports` not set
Error message
Error reading configuration: `module.exports` not set
What it means
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.
Source
Thrown at app/config/init.ts:13
import vm from 'vm';
import merge from 'lodash/merge';
import type {parsedConfig, rawConfig, configOptions} from '../../typings/config';
import notify from '../notify';
import mapKeys from '../utils/map-keys';
const _extract = (script?: vm.Script): Record<string, any> => {
const module: Record<string, any> = {};
script?.runInNewContext({module}, {displayErrors: true});
if (!module.exports) {
throw new Error('Error reading configuration: `module.exports` not set');
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return module.exports;
};
const _syntaxValidation = (cfg: string) => {
try {
return new vm.Script(cfg, {filename: '.hyper.js'});
} catch (_err) {
const err = _err as {name: string};
notify(`Error loading config: ${err.name}`, JSON.stringify(err), {error: err});
}
};
const _extractDefault = (cfg: string) => {
return _extract(_syntaxValidation(cfg));
};
View on GitHub (pinned to da0c401d7f)
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'.
Example fix
// before — ~/.hyper.js (broken)
// only wrote the object literal, no export
{
config: { fontSize: 12 }
}
// -> Error reading configuration: `module.exports` not set
// after — ~/.hyper.js (fixed, CommonJS export)
module.exports = {
config: {
fontSize: 12,
fontFamily: 'Menlo, monospace',
},
plugins: [],
localPlugins: [],
keymaps: {},
}; Defensive patterns
Strategy: try-catch
Validate before calling
// Validate the user config string BEFORE running it through _extract/_extractDefault.
// Mirror the loader's own contract: must compile as a script AND assign module.exports.
import vm from 'vm';
function isAssignableConfig(cfg: string): boolean {
if (!cfg || !cfg.trim()) return false;
let script: vm.Script;
try {
script = new vm.Script(cfg, {filename: '.hyper.js'});
} catch {
return false; // syntax error — _syntaxValidation would notify and return undefined
}
const module: Record<string, any> = {};
try {
script.runInNewContext({module}, {displayErrors: false});
} catch {
return false; // runtime error during assignment
}
return module.exports != null && typeof module.exports === 'object';
}
// usage before _extractDefault
if (!isAssignableConfig(rawUserCfgString)) {
notify('Configuration file is incomplete', 'Recreating ~/.hyper.js with defaults');
rawUserCfgString = defaultCfgString;
} Type guard
// Narrow the extracted object after a successful _extract call.
// _extract already guarantees module.exports is truthy; this guard validates shape.
import type { rawConfig } from '../../typings/config';
function isRawConfig(v: unknown): v is rawConfig {
if (typeof v !== 'object' || v === null) return false;
const r = v as Record<string, unknown>;
// config is optional at this layer but if present must be an object
if ('config' in r && (typeof r.config !== 'object' || r.config === null)) return false;
if ('plugins' in r && !Array.isArray(r.plugins)) return false;
if ('localPlugins' in r && !Array.isArray(r.localPlugins)) return false;
if ('keymaps' in r && (typeof r.keymaps !== 'object' || r.keymaps === null)) return false;
return true;
}
const extracted = _extract(script);
if (!isRawConfig(extracted)) {
throw new Error('Configuration shape invalid after extraction');
} Try / catch
// _extract throws synchronously. Wrap _extractDefault / _init callers so a broken
// user config never takes down app boot — fall back to defaults and notify.
import {_init, _extractDefault} from './config/init';
import defaultRaw from './config/config-default.json';
function safeInit(userRawString: string, defaultRawString: string) {
let userCfg;
try {
userCfg = _extractDefault(userRawString);
} catch (err) {
const e = err as Error;
if (/module\.exports not set/i.test(e.message)) {
notify('Configuration unreadable', 'Using defaults until ~/.hyper.js sets module.exports');
userCfg = _extractDefault(defaultRawString); // guaranteed-valid default
} else {
throw e; // unknown failure — do not swallow
}
}
return _init(userCfg, _extractDefault(defaultRawString));
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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).
AI-assisted analysis of vercel/hyper@da0c401d7f (2026-08-12).
Data as JSON: /api/errors/7168ada85389410d.
Report an issue: GitHub.