{"record":{"id":"346e80b89765a356","repo":"can1357/oh-my-pi","slug":"marketplace-catalog-at-filepath-must-be-a-json","errorCode":null,"errorMessage":"Marketplace catalog at ${filePath} must be a JSON object","messagePattern":"Marketplace catalog at (.+?) must be a JSON object","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/extensibility/plugins/marketplace/fetcher.ts","lineNumber":112,"sourceCode":"/**\n * Parse and validate a marketplace.json catalog from raw JSON content.\n *\n * Required fields: name (valid name segment), owner.name, plugins array.\n * Each plugin entry requires name (string) and source (string or object\n * with a \"source\" field). Extra fields are preserved via spread.\n *\n * @throws on JSON parse failure or missing/invalid required fields.\n */\nexport function parseMarketplaceCatalog(content: string, filePath: string): MarketplaceCatalog {\n\tlet raw: unknown;\n\ttry {\n\t\traw = JSON.parse(content);\n\t} catch (err) {\n\t\tthrow new Error(`Failed to parse marketplace catalog at ${filePath}: ${(err as Error).message}`);\n\t}\n\n\tif (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n\t\tthrow new Error(`Marketplace catalog at ${filePath} must be a JSON object`);\n\t}\n\n\tconst obj = raw as Record<string, unknown>;\n\n\t// name: required, must be a valid name segment\n\tassertField(typeof obj.name === \"string\" && isValidNameSegment(obj.name), \"name\", filePath);\n\n\t// owner: required object with name string\n\tassertField(typeof obj.owner === \"object\" && obj.owner !== null && !Array.isArray(obj.owner), \"owner\", filePath);\n\tconst owner = obj.owner as Record<string, unknown>;\n\tassertField(typeof owner.name === \"string\", \"owner.name\", filePath);\n\n\t// plugins: required array\n\tassertField(Array.isArray(obj.plugins), \"plugins\", filePath);\n\n\tconst plugins = obj.plugins as unknown[];\n\tconst validPlugins: unknown[] = [];\n\tfor (let i = 0; i < plugins.length; i++) {","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/extensibility/plugins/marketplace/fetcher.ts#L94-L130","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Wrap the catalog content in a JSON object with the required fields: { \"name\": \"...\", \"plugins\": [...] }.","Check the upstream marketplace for the expected catalog schema and update your file to match.","If pointing at a URL, ensure the endpoint returns the catalog object, not a list or scalar."],"exampleFix":"// before (catalog.json)\n[{ \"name\": \"plugin-a\" }]\n// after\n{ \"name\": \"my-market\", \"plugins\": [{ \"name\": \"plugin-a\" }] }","handlingStrategy":"validation","validationCode":"const parsed = JSON.parse(content);\nif (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n  throw new Error(\"Catalog must be a JSON object with name and plugins fields\");\n}\nif (typeof parsed.name !== \"string\" || !Array.isArray(parsed.plugins)) {\n  throw new Error(\"Catalog is missing required name/plugins fields\");\n}","typeGuard":"function isCatalogObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}","tryCatchPattern":null,"preventionTips":["Keep the required top-level shape: { name, plugins: [...] } — never emit a bare array.","Validate against the marketplace catalog schema in CI before publishing.","When converting from another format, always wrap results in an object."],"tags":["json","schema","marketplace","shape-validation"],"backgroundTag":"unexpected-json-root-type","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}