different-ai/openwork · error · ApiError
invalid_plugin_manifest
invalid_plugin_manifest
Error message
${manifestPath} is not valid JSON What it means
After fetching the manifest text, resolveClaudePluginBundle parses it with JSON.parse and requires the result to be a plain object. Any parse failure (malformed JSON, trailing commas, HTML error page returned instead of JSON) or non-object result throws this 400 ApiError identifying the manifest path.
Source
Thrown at apps/server/src/claude-plugin-bundle.ts:283
return false;
}
export async function resolveClaudePluginBundle(input: { url: string; ref?: string }): Promise<ClaudePluginBundle> {
const source = parseClaudePluginSource(input.url);
const { ref, dir, tree } = await resolveRefAndTree(source, input.ref?.trim() || undefined);
const root = locatePluginRoot(tree, dir);
const treeByPath = new Map(tree.map((entry) => [entry.path, entry]));
const warnings: string[] = [];
const manifestPath = `${root}.claude-plugin/plugin.json`;
const manifestText = await fetchGithubText(rawFileUrl(source, ref, manifestPath));
let manifest: Record<string, unknown>;
try {
const parsed: unknown = JSON.parse(manifestText);
if (!isRecord(parsed)) throw new Error("not an object");
manifest = parsed;
} catch {
throw new ApiError(400, "invalid_plugin_manifest", `${manifestPath} is not valid JSON`);
}
const pluginName = readString(manifest.displayName) ?? readString(manifest.name);
if (!pluginName) {
throw new ApiError(400, "invalid_plugin_manifest", `${manifestPath} is missing a plugin name`);
}
const description = readString(manifest.description);
const version = readString(manifest.version);
if (manifest.hooks !== undefined) {
warnings.push("This plugin declares hooks, which OpenWork does not support yet. Hooks were skipped.");
}
// --- Collect component file paths -----------------------------------------
const inTree = (path: string) => treeByPath.has(path);
const collectMarkdown = (declared: string[], defaultDir: string): string[] => {
const roots = declared.length > 0
? declared.map((entry) => normalizeRelative(root, entry)).filter(Boolean)View on GitHub (pinned to 2b7df46e8a)
Solutions
- Validate the plugin.json locally with `node -e "JSON.parse(require('fs').readFileSync('.claude-plugin/plugin.json'))"` or a JSON linter.
- Remove comments, trailing commas, BOM, and non-ASCII quotes; JSON does not allow JSONC syntax.
- Confirm the file at that path/ref is the real manifest and not an HTML/error page.
- Re-fetch after fixing and pushing the corrected manifest to the repo.
Example fix
// before (.claude-plugin/plugin.json)
{ "name": "my-plugin", } // trailing comma
// after
{ "name": "my-plugin" } Defensive patterns
Strategy: validation
Validate before calling
import { readFileSync } from "node:fs";
const raw = readFileSync(".claude-plugin/plugin.json", "utf8").replace(/^\uFEFF/, "");
JSON.parse(raw); // throws locally before install if invalid Type guard
function isJsonObject(v: unknown): v is Record<string, unknown> {
return typeof v === "object" && v !== null && !Array.isArray(v);
} Try / catch
try {
await installPlugin(url);
} catch (e) {
if (isApiError(e) && e.code === "invalid_plugin_manifest") {
// report the manifest path from e.message back to the plugin vendor
}
} Prevention
- Lint plugin.json with a strict JSON parser in CI.
- Never allow comments or trailing commas in manifests.
- Strip BOM and ensure UTF-8 without smart quotes.
- Fetch the manifest yourself and JSON.parse it as a pre-check.
When it happens
Trigger: The .claude-plugin/plugin.json in the repo is syntactically invalid JSON, contains comments/BOM, or the fetched text is not the manifest at all (e.g. a GitHub error page captured as text).
Common situations: Hand-edited manifest with a missing comma or trailing comma; manifest saved with comments (JSONC instead of JSON); wrong file committed with plugin.json name; encoding issues (BOM, smart quotes from editors).
Related errors
- plugin_manifest_not_found
- plugin_empty
- invalid_cloud_plugin
- ${label} syntax: enter valid JSON.
- Agent context diagnostics timeout must be between 1 ms and 3
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/7c1c20ab06791bf6.
Report an issue: GitHub.