{"record":{"id":"2f4e3e61e5af856d","repo":"ruvnet/ruflo","slug":"pod-template-must-be-a-json-object","errorCode":null,"errorMessage":"pod-template must be a JSON object","messagePattern":"pod-template must be a JSON object","errorType":"validation","errorClass":"PodTemplateValidationError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/business-pods/pod-schema.ts","lineNumber":189,"sourceCode":"}\n\n// POSIX cron — five or six space-separated fields. Permissive on field\n// contents (digits, *, -, /, ,) — actual cron evaluation happens at schedule\n// time. We only catch obviously malformed values here.\nconst CRON_RE = /^([\\d*/,\\-]+\\s+){4,5}[\\d*/,\\-]+$/;\n\n/**\n * Validate `json` and return a typed `PodTemplate`. Throws\n * `PodTemplateValidationError` with a JSON-pointer-style path on failure.\n *\n * Used by:\n *   - `business_pod_validate` MCP tool — returns the error verbatim\n *   - `pod-tick.mjs` — pre-flight check before any pod execution\n *   - any external schema-loader that wants typed templates\n */\nexport function validatePodTemplate(json: unknown): PodTemplate {\n  if (!isObject(json)) {\n    throw new PodTemplateValidationError('pod-template must be a JSON object', '/');\n  }\n  const name = requireString(json, 'name', '/');\n  if (!/^[a-z][a-z0-9-]*$/.test(name)) {\n    throw new PodTemplateValidationError('name must be lowercase-kebab (e.g. \"sales\")', '/');\n  }\n  const displayName = requireString(json, 'displayName', '/');\n  const roomId = requireString(json, 'roomId', '/');\n  if (!/^[A-Za-z0-9_.\\-:/@#]+$/.test(roomId)) {\n    throw new PodTemplateValidationError(\n      'roomId may only contain [A-Za-z0-9_.\\\\-:/@#]',\n      '/',\n    );\n  }\n  const agents = requireArray(json, 'agents', '/', validatePodAgent);\n  if (agents.length === 0) {\n    throw new PodTemplateValidationError('agents must have ≥1 entry', '/');\n  }\n  const allowedMcpTools = requireArray(json, 'allowedMcpTools', '/', (t, tp) => {","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/cli/src/business-pods/pod-schema.ts#L171-L207","documentation":"The very first check in validatePodTemplate(): the parsed value must be a plain JSON object. Arrays, null, strings (including double-encoded JSON), and numbers are rejected at path '/'. Everything else in the schema depends on this root being a mapping of pod-template fields.","triggerScenarios":"Calling validatePodTemplate() on a JSON file whose root is an array (e.g. a list of pod templates), on a raw YAML/JSON string that was never JSON.parse'd, or on JSON.stringify'd twice (producing a string containing JSON).","commonSituations":"Multi-pod files where each element is a template; loaders that read the file as text and pass it unvalidated; double-encoding bugs in template generators; passing fs.readFileSync output directly.","solutions":["If the file is a list of templates, iterate and validate each element individually","Make sure you JSON.parse(raw) exactly once before calling validatePodTemplate()","Unwrap double-encoded JSON: if typeof json === 'string', parse it again, then validate"],"exampleFix":"// before\nconst raw = await readFile('pod.json', 'utf-8');\nvalidatePodTemplate(raw); // raw is a string -> throws\n// after\nconst raw = await readFile('pod.json', 'utf-8');\nvalidatePodTemplate(JSON.parse(raw));","handlingStrategy":"type-guard","validationCode":"const parsed = JSON.parse(raw);\nif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n  if (Array.isArray(parsed)) { /* validate each element instead */ }\n  else throw new Error('pod template root must be a JSON object');\n}","typeGuard":"function isPlainObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === 'object' && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try { validatePodTemplate(parsed); } catch (err) {\n  if (err instanceof PodTemplateValidationError && err.path === '/') {\n    // root shape wrong — check for double-encoded JSON or a list-of-pods file\n    if (typeof parsed === 'string') validatePodTemplate(JSON.parse(parsed));\n  }\n}","preventionTips":["Always JSON.parse file contents exactly once before validating","If templates ship as arrays, unwrap and validate each element in a loop","Add a smoke test that validates every committed template file in CI"],"tags":["schema-validation","business-pods","json","pod-template"],"backgroundTag":"schema-validation-failed","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}