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
- Ensure plugin.js ends with a default export of an object: `export default { id: 'my-plugin', name: 'My Plugin', register(ctx) { ... } }`.
- Verify `id` is a non-empty stable string and `register` is a function taking the ctx.
- If the file uses named exports or CJS, add the explicit default export re-exporting the plugin object.
- 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
- Always `export default { id, register }` — named exports and function defaults fail
- Keep id non-empty and stable across reloads
- Add a satisfies/type check on the exported object in plugin source
- Use the loader's null return instead of exceptions for control flow
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
- unsupported import${unsupported.length > 1 ? 's' : ''}: ${un
- Not a directory: ${watchDir}
- Branch name is required.
- Missing URL
- Invalid data URL
AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14).
Data as JSON: /api/errors/71d0a96d0787c4e2.
Report an issue: GitHub.