moeru-ai/airi · error · Error

Extension manifest is not valid JSON

Error message

Extension manifest is not valid JSON: ${error.message}

What it means

The manifest snapshot bytes are parsed with JSON.parse; if parsing raises a SyntaxError it is rethrown with a clearer message. This means the manifest file exists and was read successfully but its bytes are not syntactically valid JSON.

Solutions

  1. Validate manifest.json with a JSON linter/parser (`node -e "JSON.parse(require('fs').readFileSync('manifest.json'))"` or jq) and fix the syntax error reported in the message.
  2. Remove comments and trailing commas — the parser is strict JSON, not JSON5/JSONC.
  3. Re-save the file as UTF-8 without BOM; ensure it is not UTF-16.
  4. Restore the manifest from version control or re-extract the package if the file is truncated.

Example fix

// before: manifest.json
{ "name": "x", "entrypoints": {...}, } // trailing comma -> throws
// after
{ "name": "x", "entrypoints": {...} }
Defensive patterns

Strategy: validation

Validate before calling

import { readFile } from 'node:fs/promises'
function assertValidManifestJson(path: string) {
  const text = (await readFile(path, 'utf8')).replace(/^\uFEFF/, '')
  JSON.parse(text) // throws SyntaxError with position if invalid
}

Type guard

function isParseableJson(text: string): boolean {
  try { JSON.parse(text.replace(/^\uFEFF/, '')); return true }
  catch { return false }
}

Try / catch

try {
  await inspected(folder)
} catch (error) {
  if (error instanceof Error && error.message.startsWith('Extension manifest is not valid JSON')) {
    showUserError(`Fix manifest.json syntax: ${error.message}`)
    return
  }
  throw error
}

Prevention

When it happens

Trigger: Calling inspectExtensionDirectory when manifest.json contains malformed JSON — trailing commas, unquoted keys, comments, BOM-related issues, truncated writes, or wrong encoding (UTF-16).

Common situations: A hand-edited manifest with a missing comma or quote; a JSON file with // comments (JSON5-style) that strict JSON.parse rejects; an interrupted save left a truncated file; the file was saved with a UTF-8 BOM or non-UTF8 encoding.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


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

Appendix: source

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

  if (!manifestFile) {
    throw new Error(`Extension manifest does not exist: ${manifestPath}`)
  }
  if (manifestFile.size > extensionPackageLimits.manifestBytes) {
    throw new Error('Extension manifest exceeds the 1 MiB size limit.')
  }

  // 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)) {

View on GitHub (pinned to 438a067dde)