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

  1. Make the entrypoint end with export default defineExtension({ id: '...', setup(...) {...} }) — the loader checks the namespace and its .default only.
  2. 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.
  3. Fix bundler config so the default export survives (output.exports: 'default' / esModuleInterop) and avoid double-default wrappers.
  4. 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

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


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)