moeru-ai/airi · error · Error

Extension manifest is invalid

Error message

Extension manifest is invalid: ${formatManifestDiagnostics(parsedManifest.diagnostics)}

What it means

After JSON parsing succeeds, the raw value is validated with parseExtensionManifest (a Valibot-based schema parser). If the schema check fails, the collected diagnostics are formatted into this error. The manifest is syntactically valid JSON but does not match the required extension manifest schema (missing/misspelled/incorrectly-typed fields).

Solutions

  1. Read the diagnostics in the message — they pinpoint each failing field — and fix the manifest to match the current extension manifest schema.
  2. Compare against a known-good example manifest from the docs or another working extension.
  3. Check for typos in field names and ensure required fields exist with correct types.
  4. Update the extension to the current manifest format if it targets an older schema version.

Example fix

// before: manifest.json
{ "entrypoints": { "main": "index.js" } } // entrypoint must be object, not string
// after
{ "name": "my-ext", "entrypoints": { "main": { "path": "index.js" } } }
Defensive patterns

Strategy: validation

Validate before calling

import { parseExtensionManifest } from '@proj-airi/...' // whatever module owns the schema
const raw = JSON.parse(await readFile(join(folder, 'manifest.json'), 'utf8'))
const parsed = parseExtensionManifest(raw)
if (!parsed.success)
  console.error(formatManifestDiagnostics(parsed.diagnostics)) // fix before importing

Type guard

function isValidManifest(value: unknown): value is ExtensionManifest {
  return parseExtensionManifest(value).success
}

Try / catch

try {
  await inspected(folder)
} catch (error) {
  if (error instanceof Error && error.message.startsWith('Extension manifest is invalid')) {
    showUserError(`Manifest schema errors: ${error.message}`)
    return
  }
  throw error
}

Prevention

When it happens

Trigger: Calling inspectExtensionDirectory when manifest.json parses as JSON but fails schema validation — missing required fields like name or entrypoints, wrong value types (number where string expected), unknown enum values, or nested entrypoint objects failing their own constraints.

Common situations: Hand-writing a manifest without the schema reference; an older extension using a previous manifest format; a field typo ("entypoints"); entrypoints entries with wrong shape; version drift after the host added stricter validation.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of moeru-ai/airi@438a067dde (2026-09-17). Data as JSON: /api/errors/7eba037bcb719ca2. Report an issue: GitHub.

Appendix: source

Thrown at apps/stage-tamagotchi/src/main/services/airi/plugins/host/directory-import.ts:231

  // The manifest remains the only retained byte snapshot because the preview
  // and fingerprint must describe the same manifest contents. The stable file
  // handle limits the allocation even if another process grows the path.
  const manifestContents = await readManifestSnapshot(manifestFile.path, manifestFile.size)

  let rawManifest: unknown
  try {
    rawManifest = JSON.parse(manifestContents.toString('utf8')) as unknown
  }
  catch (error) {
    if (error instanceof SyntaxError) {
      throw new Error(`Extension manifest is not valid JSON: ${error.message}`)
    }
    throw error
  }

  const parsedManifest = parseExtensionManifest(rawManifest)
  if (!parsedManifest.success) {
    throw new Error(`Extension manifest is invalid: ${formatManifestDiagnostics(parsedManifest.diagnostics)}`)
  }

  for (const entrypoint of Object.values(parsedManifest.manifest.entrypoints)) {
    if (!entrypoint) {
      continue
    }
    if (isAbsolute(entrypoint)) {
      throw new Error(`Imported Extension entrypoints must be relative paths: ${entrypoint}`)
    }
    const resolvedEntrypoint = resolve(sourceRealPath, entrypoint)
    if (!isContainedPath(sourceRealPath, resolvedEntrypoint)) {
      throw new Error(`Extension entrypoint escapes the package folder: ${entrypoint}`)
    }
    await assertRegularFile(resolvedEntrypoint, 'Extension entrypoint')
    const entrypointRealPath = await realpath(resolvedEntrypoint)
    if (!isContainedPath(sourceRealPath, entrypointRealPath)) {
      throw new Error(`Extension entrypoint resolves outside the package folder: ${entrypoint}`)
    }

View on GitHub (pinned to 438a067dde)