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
- 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.
- Remove comments and trailing commas — the parser is strict JSON, not JSON5/JSONC.
- Re-save the file as UTF-8 without BOM; ensure it is not UTF-16.
- 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
- Validate manifest.json with jq or a JSON linter in your build pipeline.
- Author manifests in a JSON-aware editor; never append comments or trailing commas.
- Save as UTF-8 without BOM; strip BOMs before shipping.
- Don't hand-edit JSON in editors without syntax checking.
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
- Expected `cap run --list --json` to return a JSON array.
- Extension entrypoint escapes the package folder
- Extension entrypoint id \
- Extension manifest does not exist
- Extension \` \` does not support runtime \` \`.
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)