can1357/oh-my-pi · error · Error

Marketplace catalog at ${filePath} must be a JSON object

Error message

Marketplace catalog at ${filePath} must be a JSON object

What it means

After a successful JSON.parse, parseMarketplaceCatalog requires the parsed value to be a plain object. Arrays, strings, numbers, booleans, and null are rejected because a marketplace catalog must carry named fields (name, plugins, ...). The file parses fine but has the wrong top-level shape.

Source

Thrown at packages/coding-agent/src/extensibility/plugins/marketplace/fetcher.ts:112

/**
 * Parse and validate a marketplace.json catalog from raw JSON content.
 *
 * Required fields: name (valid name segment), owner.name, plugins array.
 * Each plugin entry requires name (string) and source (string or object
 * with a "source" field). Extra fields are preserved via spread.
 *
 * @throws on JSON parse failure or missing/invalid required fields.
 */
export function parseMarketplaceCatalog(content: string, filePath: string): MarketplaceCatalog {
	let raw: unknown;
	try {
		raw = JSON.parse(content);
	} catch (err) {
		throw new Error(`Failed to parse marketplace catalog at ${filePath}: ${(err as Error).message}`);
	}

	if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
		throw new Error(`Marketplace catalog at ${filePath} must be a JSON object`);
	}

	const obj = raw as Record<string, unknown>;

	// name: required, must be a valid name segment
	assertField(typeof obj.name === "string" && isValidNameSegment(obj.name), "name", filePath);

	// owner: required object with name string
	assertField(typeof obj.owner === "object" && obj.owner !== null && !Array.isArray(obj.owner), "owner", filePath);
	const owner = obj.owner as Record<string, unknown>;
	assertField(typeof owner.name === "string", "owner.name", filePath);

	// plugins: required array
	assertField(Array.isArray(obj.plugins), "plugins", filePath);

	const plugins = obj.plugins as unknown[];
	const validPlugins: unknown[] = [];
	for (let i = 0; i < plugins.length; i++) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Wrap the catalog content in a JSON object with the required fields: { "name": "...", "plugins": [...] }.
  2. Check the upstream marketplace for the expected catalog schema and update your file to match.
  3. If pointing at a URL, ensure the endpoint returns the catalog object, not a list or scalar.

Example fix

// before (catalog.json)
[{ "name": "plugin-a" }]
// after
{ "name": "my-market", "plugins": [{ "name": "plugin-a" }] }
Defensive patterns

Strategy: validation

Validate before calling

const parsed = JSON.parse(content);
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
  throw new Error("Catalog must be a JSON object with name and plugins fields");
}
if (typeof parsed.name !== "string" || !Array.isArray(parsed.plugins)) {
  throw new Error("Catalog is missing required name/plugins fields");
}

Type guard

function isCatalogObject(v: unknown): v is Record<string, unknown> {
  return typeof v === "object" && v !== null && !Array.isArray(v);
}

Prevention

When it happens

Trigger: parseMarketplaceCatalog called with content that parses to a JSON array (e.g. the file contains only the plugins list), a bare string/number, or null.

Common situations: Writing just the plugins array to catalog.json instead of wrapping it in an object; a upstream repo restructuring the catalog to a different top-level shape; an API endpoint returning a JSON array of catalogs.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/346e80b89765a356. Report an issue: GitHub.