{"record":{"id":"7eba037bcb719ca2","repo":"moeru-ai/airi","slug":"extension-manifest-is-invalid-formatmanifestdiagnostics","errorCode":null,"errorMessage":"Extension manifest is invalid: ${formatManifestDiagnostics(parsedManifest.diagnostics)}","messagePattern":"Extension manifest is invalid: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/stage-tamagotchi/src/main/services/airi/plugins/host/directory-import.ts","lineNumber":231,"sourceCode":"  // The manifest remains the only retained byte snapshot because the preview\n  // and fingerprint must describe the same manifest contents. The stable file\n  // handle limits the allocation even if another process grows the path.\n  const manifestContents = await readManifestSnapshot(manifestFile.path, manifestFile.size)\n\n  let rawManifest: unknown\n  try {\n    rawManifest = JSON.parse(manifestContents.toString('utf8')) as unknown\n  }\n  catch (error) {\n    if (error instanceof SyntaxError) {\n      throw new Error(`Extension manifest is not valid JSON: ${error.message}`)\n    }\n    throw error\n  }\n\n  const parsedManifest = parseExtensionManifest(rawManifest)\n  if (!parsedManifest.success) {\n    throw new Error(`Extension manifest is invalid: ${formatManifestDiagnostics(parsedManifest.diagnostics)}`)\n  }\n\n  for (const entrypoint of Object.values(parsedManifest.manifest.entrypoints)) {\n    if (!entrypoint) {\n      continue\n    }\n    if (isAbsolute(entrypoint)) {\n      throw new Error(`Imported Extension entrypoints must be relative paths: ${entrypoint}`)\n    }\n    const resolvedEntrypoint = resolve(sourceRealPath, entrypoint)\n    if (!isContainedPath(sourceRealPath, resolvedEntrypoint)) {\n      throw new Error(`Extension entrypoint escapes the package folder: ${entrypoint}`)\n    }\n    await assertRegularFile(resolvedEntrypoint, 'Extension entrypoint')\n    const entrypointRealPath = await realpath(resolvedEntrypoint)\n    if (!isContainedPath(sourceRealPath, entrypointRealPath)) {\n      throw new Error(`Extension entrypoint resolves outside the package folder: ${entrypoint}`)\n    }","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/moeru-ai/airi/blob/438a067dde47aa0bdb46c2323d1fe293dc805218/apps/stage-tamagotchi/src/main/services/airi/plugins/host/directory-import.ts#L213-L249","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the diagnostics in the message — they pinpoint each failing field — and fix the manifest to match the current extension manifest schema.","Compare against a known-good example manifest from the docs or another working extension.","Check for typos in field names and ensure required fields exist with correct types.","Update the extension to the current manifest format if it targets an older schema version."],"exampleFix":"// before: manifest.json\n{ \"entrypoints\": { \"main\": \"index.js\" } } // entrypoint must be object, not string\n// after\n{ \"name\": \"my-ext\", \"entrypoints\": { \"main\": { \"path\": \"index.js\" } } }","handlingStrategy":"validation","validationCode":"import { parseExtensionManifest } from '@proj-airi/...' // whatever module owns the schema\nconst raw = JSON.parse(await readFile(join(folder, 'manifest.json'), 'utf8'))\nconst parsed = parseExtensionManifest(raw)\nif (!parsed.success)\n  console.error(formatManifestDiagnostics(parsed.diagnostics)) // fix before importing","typeGuard":"function isValidManifest(value: unknown): value is ExtensionManifest {\n  return parseExtensionManifest(value).success\n}","tryCatchPattern":"try {\n  await inspected(folder)\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith('Extension manifest is invalid')) {\n    showUserError(`Manifest schema errors: ${error.message}`)\n    return\n  }\n  throw error\n}","preventionTips":["Run the same parseExtensionManifest schema check in your extension's CI before release.","Keep the manifest aligned with the current host schema version; re-validate after host updates.","Start from a known-good example manifest instead of writing one from scratch.","Read the formatted diagnostics — they name each offending field and expected type."],"tags":["manifest","validation","schema","extensions"],"backgroundTag":"schema-validation-failed","analyzedSha":"438a067dde47aa0bdb46c2323d1fe293dc805218","analyzedAt":"2026-09-17T01:14:42.644Z","contentChangedAt":"2026-09-17T01:14:42.644Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}