{"record":{"id":"71d0a96d0787c4e2","repo":"NousResearch/hermes-agent","slug":"origin-has-no-valid-default-hermesplugin-export","errorCode":null,"errorMessage":"${origin} has no valid default HermesPlugin export","messagePattern":"(.+?) has no valid default HermesPlugin export","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/src/contrib/runtime-loader.ts","lineNumber":142,"sourceCode":"        `unsupported import${unsupported.length > 1 ? 's' : ''}: ${unsupported.join(', ')} — ` +\n          `runtime plugins may only import @hermes/plugin-sdk and react`\n      )\n    }\n\n    const url = URL.createObjectURL(new Blob([rewriteSpecifiers(source)], { type: 'text/javascript' }))\n\n    let mod: { default?: HermesPlugin }\n\n    try {\n      mod = await import(/* @vite-ignore */ url)\n    } finally {\n      URL.revokeObjectURL(url)\n    }\n\n    const plugin = mod.default\n\n    if (!plugin?.id || typeof plugin.register !== 'function') {\n      throw new Error(`${origin} has no valid default HermesPlugin export`)\n    }\n\n    const record = {\n      id: plugin.id,\n      name: plugin.name ?? plugin.id,\n      description: plugin.description,\n      kind: options.kind ?? 'disk',\n      file: options.file\n    }\n\n    const activate = () => {\n      // Reload = dispose the previous incarnation, then register fresh.\n      unloadRuntimePlugin(plugin.id)\n      const disposers: (() => void)[] = []\n      plugin.register(createPluginContext(plugin.id, dispose => disposers.push(dispose)))\n      loaded.set(plugin.id, disposers)\n      publishPlugin({ ...record, status: 'loaded' })\n    }","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/src/contrib/runtime-loader.ts#L124-L160","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nexport function register(ctx) { ctx.toast('hi') }\n\n// after\nexport default {\n  id: 'hello-plugin',\n  name: 'Hello',\n  register(ctx) { ctx.toast('hi') }\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isHermesPlugin(v: unknown): v is { id: string; register: (ctx: unknown) => void } {\n  return typeof v === 'object' && v !== null\n    && typeof (v as any).id === 'string' && (v as any).id.length > 0\n    && typeof (v as any).register === 'function'\n}\n// inside plugin.js itself, guarantee the shape at export time:\nconst plugin = { id: 'x', register(ctx) { /* ... */ } }\nexport default plugin satisfies HermesPlugin","tryCatchPattern":"const id = await loadRuntimePlugin(src, origin) // returns null on failure\nif (id === null) { check the plugin inventory row's error field; loader already toasts }","preventionTips":["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"],"tags":["desktop","plugins","exports","validation","esm"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}