{"record":{"id":"5d63c5f20a62b5db","repo":"moeru-ai/airi","slug":"failed-to-resolve-extension-module-the-entrypoint","errorCode":null,"errorMessage":"Failed to resolve extension module. The entrypoint must export defineExtension(...).","messagePattern":"Failed to resolve extension module\\. The entrypoint must export defineExtension\\(\\.\\.\\.\\)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugin-sdk/src/plugin-host/runtimes/node/loaders/fs.ts","lineNumber":28,"sourceCode":"    && 'id' in value\n    && typeof (value as { id?: unknown }).id === 'string'\n    && 'setup' in value\n    && typeof (value as { setup?: unknown }).setup === 'function'\n}\n\nfunction coerceExtensionFromModule(moduleValue: unknown): Extension {\n  if (isExtensionDefinition(moduleValue)) {\n    return moduleValue\n  }\n\n  if (typeof moduleValue === 'object' && moduleValue !== null) {\n    const defaultExport = (moduleValue as { default?: unknown }).default\n    if (isExtensionDefinition(defaultExport)) {\n      return defaultExport\n    }\n  }\n\n  throw new Error('Failed to resolve extension module. The entrypoint must export defineExtension(...).')\n}\n\n/**\n * Loads extension entrypoints from the local filesystem for the current runtime.\n *\n * Use when:\n * - The host needs to resolve a manifest entrypoint path\n * - The host needs to import a `defineExtension(...)` export\n *\n * Expects:\n * - Entry points are valid importable module paths for the active runtime\n *\n * Returns:\n * - Filesystem-backed helpers for resolving and loading extension entrypoints\n */\nexport class FileSystemLoader {\n  /**\n   * Resolve a manifest entrypoint for the requested runtime.","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/moeru-ai/airi/blob/438a067dde47aa0bdb46c2323d1fe293dc805218/packages/plugin-sdk/src/plugin-host/runtimes/node/loaders/fs.ts#L10-L46","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (entrypoint)\nexport const extension = { id: 'my-ext', setup: async () => {} } // named, plain object -> throws\n\n// after\nimport { defineExtension } from '../extension'\nexport default defineExtension({ id: 'my-ext', setup: async () => {} })","handlingStrategy":"type-guard","validationCode":"// After importing the entrypoint yourself, before/instead of relying on the loader:\nconst mod = await import(entrypoint)\nconst candidate = (mod as { default?: unknown }).default ?? mod\nconst ok = typeof candidate === 'object' && candidate !== null\n  && typeof (candidate as { id?: unknown }).id === 'string'\n  && typeof (candidate as { setup?: unknown }).setup === 'function'\nif (!ok) throw new Error(`${entrypoint} must default-export defineExtension(...)`)","typeGuard":"function isExtensionDefinition(value: unknown): value is Extension {\n  return typeof value === 'object' && value !== null\n    && typeof (value as { id?: unknown }).id === 'string'\n    && typeof (value as { setup?: unknown }).setup === 'function'\n}","tryCatchPattern":"try {\n  extension = await loader.loadExtensionFor(manifest, options)\n} catch (error) {\n  if (error instanceof Error && error.message.includes('must export defineExtension')) {\n    // entrypoint shape problem: fix the module's default export or the manifest path; not retryable\n    throw new Error(`Bad extension entrypoint for ${manifest.id}: ${error.message}`)\n  }\n  throw error\n}","preventionTips":["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)."],"tags":["plugin-sdk","loader","entrypoint","module-exports","defineextension"],"backgroundTag":"missing-default-export","analyzedSha":"438a067dde47aa0bdb46c2323d1fe293dc805218","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}