NousResearch/hermes-agent · error

${origin} has no valid default HermesPlugin export

Error message

${origin} has no valid default HermesPlugin export

What it means

Thrown by the runtime-plugin loader (runtime-loader.ts:142) after the plugin module evaluates successfully but its default export is not a valid HermesPlugin: either `mod.default` is missing, `plugin.id` is falsy, or `plugin.register` is not a function. The loader's contract is a default-exported object `{ id, name?, description?, register(ctx), defaultEnabled? }`. A module that only does side effects, exports a function as default, or exports under a named key fails here.

Source

Thrown at apps/desktop/src/contrib/runtime-loader.ts:142

        `unsupported import${unsupported.length > 1 ? 's' : ''}: ${unsupported.join(', ')} — ` +
          `runtime plugins may only import @hermes/plugin-sdk and react`
      )
    }

    const url = URL.createObjectURL(new Blob([rewriteSpecifiers(source)], { type: 'text/javascript' }))

    let mod: { default?: HermesPlugin }

    try {
      mod = await import(/* @vite-ignore */ url)
    } finally {
      URL.revokeObjectURL(url)
    }

    const plugin = mod.default

    if (!plugin?.id || typeof plugin.register !== 'function') {
      throw new Error(`${origin} has no valid default HermesPlugin export`)
    }

    const record = {
      id: plugin.id,
      name: plugin.name ?? plugin.id,
      description: plugin.description,
      kind: options.kind ?? 'disk',
      file: options.file
    }

    const activate = () => {
      // Reload = dispose the previous incarnation, then register fresh.
      unloadRuntimePlugin(plugin.id)
      const disposers: (() => void)[] = []
      plugin.register(createPluginContext(plugin.id, dispose => disposers.push(dispose)))
      loaded.set(plugin.id, disposers)
      publishPlugin({ ...record, status: 'loaded' })
    }

View on GitHub (pinned to c896c09c42)

Solutions

  1. Ensure plugin.js ends with a default export of an object: `export default { id: 'my-plugin', name: 'My Plugin', register(ctx) { ... } }`.
  2. Verify `id` is a non-empty stable string and `register` is a function taking the ctx.
  3. If the file uses named exports or CJS, add the explicit default export re-exporting the plugin object.
  4. Check the plugin inventory/settings row — the failed plugin shows status 'error' with this message; fixing the file hot-reloads via the fs watcher.

Example fix

// before
export function register(ctx) { ctx.toast('hi') }

// after
export default {
  id: 'hello-plugin',
  name: 'Hello',
  register(ctx) { ctx.toast('hi') }
}
Defensive patterns

Strategy: type-guard

Type guard

function isHermesPlugin(v: unknown): v is { id: string; register: (ctx: unknown) => void } {
  return typeof v === 'object' && v !== null
    && typeof (v as any).id === 'string' && (v as any).id.length > 0
    && typeof (v as any).register === 'function'
}
// inside plugin.js itself, guarantee the shape at export time:
const plugin = { id: 'x', register(ctx) { /* ... */ } }
export default plugin satisfies HermesPlugin

Try / catch

const id = await loadRuntimePlugin(src, origin) // returns null on failure
if (id === null) { check the plugin inventory row's error field; loader already toasts }

Prevention

When it happens

Trigger: plugin.js with `export const plugin = {...}` but no default export; `export default () => {...}` (function instead of object); default export object missing `id` (empty string/undefined) or with `register` misspelled or omitted; top-level `throw`-free module that just calls host APIs at import time.

Common situations: Hand-writing a first plugin from memory of a different plugin ABI; the agent generating a CommonJS-style `module.exports = ...` file; copy-pasting from a sample that used a named export; a plugin whose default export is conditionally created and the condition evaluates to undefined.

Related errors


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