NousResearch/hermes-agent · error

default export must be register(sdk)

Error message

default export must be register(sdk)

What it means

The dashboard/TUI user-widgets loader dynamically imports every JS file in the user widgets directory and calls its default export with the WidgetSdk instance. This error means the imported module's default export is missing or not a function, so the loader cannot register any widgets from it. It is thrown per-file during discovery, before mod.default(widgetSdk) runs.

Source

Thrown at ui-tui/src/sdk/userWidgets.ts:122

      for (const id of ids) {
        if (removeWidgetApp(id)) {
          result.removed.push(id)
        }
      }
    }
  }

  for (const file of files) {
    const before = new Set(listWidgetApps().map(app => app.id))

    try {
      const mod = (await import(`${pathToFileURL(join(dir, file)).href}?t=${Date.now()}`)) as {
        default?: (sdk: WidgetSdk) => void
      }

      if (typeof mod.default !== 'function') {
        throw new Error('default export must be register(sdk)')
      }

      mod.default(widgetSdk)
      result.loaded.push(file)

      const ids = listWidgetApps()
        .map(app => app.id)
        .filter(id => !before.has(id))

      // Re-registrations of existing ids keep their prior file attribution.
      if (ids.length) {
        fileApps.set(file, ids)
        result.added.push(...ids)
      }
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error)

      result.errors.push({ file, message })

View on GitHub (pinned to c896c09c42)

Solutions

  1. Change the widget file to `export default (sdk) => { sdk.registerWidget(...) }` — a default-exported function taking the sdk.
  2. If the file is not meant to be a widget (e.g. a shared helper), move it out of the scanned widgets directory or give it an extension the loader ignores.
  3. If bundling the widget, verify the bundle retains a function default export (`output.libraryExport: 'default'` or equivalent).

Example fix

// before
export const register = (sdk) => {
  sdk.registerWidget({ id: 'clock' })
}

// after
export default (sdk) => {
  sdk.registerWidget({ id: 'clock' })
}
Defensive patterns

Strategy: type-guard

Validate before calling

const mod: { default?: unknown } = await import(url)
const isRegister = (v: unknown): v is (sdk: WidgetSdk) => void =>
  typeof v === 'function'
if (!isRegister(mod.default)) {
  console.warn(`skipping ${file}: no default register(sdk) export`)
  continue
}

Type guard

function isRegisterExport(mod: unknown): mod is { default: (sdk: WidgetSdk) => void } {
  return typeof (mod as { default?: unknown })?.default === 'function'
}

Try / catch

// loader already wraps each file; keep per-file isolation so one bad widget never aborts discovery
try {
  const mod = await import(url)
  if (typeof mod.default !== 'function') throw new Error('default export must be register(sdk)')
  mod.default(widgetSdk)
} catch (err) {
  result.failed.push({ file, error: String(err) }) // skip file, continue loop
}

Prevention

When it happens

Trigger: A file in the widgets directory (a) has no `export default`, (b) exports a default object/const instead of a function, or (c) uses `export default register` where register is undefined at module-evaluation time. Triggered by `await import(pathToFileURL(...)?t=...)` followed by `typeof mod.default !== 'function'`.

Common situations: Authoring a widget as `export const register = (sdk) => {}` (named instead of default), exporting a configuration object, or a half-written/placeholder widget file dropped into the widgets dir. Also CommonJS modules or transpiled bundles whose default export lands under `module.exports.default`.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/451a8b4b548611df. Report an issue: GitHub.