moeru-ai/airi · error · Error
Failed to resolve extension module. The entrypoint must…
Error message
Failed to resolve extension module. The entrypoint must export defineExtension(...).
What it means
Thrown by FileSystemLoader.loadExtensionFor via coerceExtensionFromModule after the entrypoint module was imported: neither the module namespace nor its default export satisfies isExtensionDefinition (an object with string id and function setup). The fs loader only recognizes an Extension produced by defineExtension(...) exposed as the module's default export, so any other module shape fails here.
Solutions
- Make the entrypoint end with export default defineExtension({ id: '...', setup(...) {...} }) — the loader checks the namespace and its .default only.
- Verify the built artifact: node -e "import('./dist/index.js').then(m => console.log(m.default))" must show the extension object with string id and function setup.
- Fix bundler config so the default export survives (output.exports: 'default' / esModuleInterop) and avoid double-default wrappers.
- Correct the manifest entrypoints path if it points at a non-extension module.
Example fix
// before (entrypoint)
export const extension = { id: 'my-ext', setup: async () => {} } // named, plain object -> throws
// after
import { defineExtension } from '../extension'
export default defineExtension({ id: 'my-ext', setup: async () => {} }) Defensive patterns
Strategy: type-guard
Validate before calling
// After importing the entrypoint yourself, before/instead of relying on the loader:
const mod = await import(entrypoint)
const candidate = (mod as { default?: unknown }).default ?? mod
const ok = typeof candidate === 'object' && candidate !== null
&& typeof (candidate as { id?: unknown }).id === 'string'
&& typeof (candidate as { setup?: unknown }).setup === 'function'
if (!ok) throw new Error(`${entrypoint} must default-export defineExtension(...)`) Type guard
function isExtensionDefinition(value: unknown): value is Extension {
return typeof value === 'object' && value !== null
&& typeof (value as { id?: unknown }).id === 'string'
&& typeof (value as { setup?: unknown }).setup === 'function'
} Try / catch
try {
extension = await loader.loadExtensionFor(manifest, options)
} catch (error) {
if (error instanceof Error && error.message.includes('must export defineExtension')) {
// entrypoint shape problem: fix the module's default export or the manifest path; not retryable
throw new Error(`Bad extension entrypoint for ${manifest.id}: ${error.message}`)
}
throw error
} Prevention
- End every extension entrypoint with export default defineExtension({...}).
- Smoke-test built artifacts in CI: import dist and assert isExtensionDefinition(module.default).
- Check bundler output so the default export is preserved (no double-default interop wrappers).
When it happens
Trigger: Entrypoint file with no default export; exporting the result under a named binding only (export const ext = defineExtension(...)); entrypoint re-exporting a barrel that loses the default; a bundler (CJS interop, tsup, esbuild) changing the namespace shape so .default is a nested accessor; entrypoints path pointing at a utility module instead of the extension file.
Common situations: Hand-written manifest entrypoints pointing at dist/ bundles whose default export was renamed or wrapped; ESM/CJS interop producing { default: { default: ... } }; forgetting defineExtension entirely and exporting a plain object; refactors that move the extension object into a helper module.
Related errors
- Extension entrypoint is required for runtime
- Cannot declare permissions for unknown plugin
- Cannot grant permissions to unknown plugin
- Electron transport is not available in web runtime.
- Electron transport is not implemented yet.
AI-assisted analysis of moeru-ai/airi@438a067dde (2026-08-18).
Data as JSON: /api/errors/5d63c5f20a62b5db.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugin-sdk/src/plugin-host/runtimes/node/loaders/fs.ts:28
&& 'id' in value
&& typeof (value as { id?: unknown }).id === 'string'
&& 'setup' in value
&& typeof (value as { setup?: unknown }).setup === 'function'
}
function coerceExtensionFromModule(moduleValue: unknown): Extension {
if (isExtensionDefinition(moduleValue)) {
return moduleValue
}
if (typeof moduleValue === 'object' && moduleValue !== null) {
const defaultExport = (moduleValue as { default?: unknown }).default
if (isExtensionDefinition(defaultExport)) {
return defaultExport
}
}
throw new Error('Failed to resolve extension module. The entrypoint must export defineExtension(...).')
}
/**
* Loads extension entrypoints from the local filesystem for the current runtime.
*
* Use when:
* - The host needs to resolve a manifest entrypoint path
* - The host needs to import a `defineExtension(...)` export
*
* Expects:
* - Entry points are valid importable module paths for the active runtime
*
* Returns:
* - Filesystem-backed helpers for resolving and loading extension entrypoints
*/
export class FileSystemLoader {
/**
* Resolve a manifest entrypoint for the requested runtime.View on GitHub (pinned to 438a067dde)