{"record":{"id":"4a9c10150c5b0f7b","repo":"ruvnet/ruflo","slug":"pod-template-at-path-pod-template-must-be-a-js","errorCode":null,"errorMessage":"pod-template at ${path}: pod-template must be a JSON object","messagePattern":"pod-template at (.+?): 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/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/business-pods/pod-schema.ts#L171-L207","documentation":"Thrown by validatePodTemplate() when the parsed JSON input is not a plain object (it is an array, string, number, boolean, or null). This is the very first structural gate in the pod-template schema validator (ADR-164 §3.3). The error is a PodTemplateValidationError whose constructor prepends 'pod-template at ${path}: ' to the message, so the final Error.message reads 'pod-template at /: pod-template must be a JSON object'.","triggerScenarios":"Calling validatePodTemplate(json) where json is JSON.parse of a JSON array, a bare string, a number, or null. Also triggered when a YAML-to-JSON converter yields a top-level scalar or when a file is accidentally read as raw text rather than parsed JSON.","commonSituations":"A pod-template JSON file under plugins/ruflo-business-pods/templates/ contains a top-level array instead of an object; the business_pod_validate MCP tool is pointed at the wrong file; a YAML front-matter block was not stripped before JSON.parse.","solutions":["Ensure the file being validated is a JSON object literal starting with '{' and ending with '}'","Check that JSON.parse is called on the file contents before passing to validatePodTemplate","Remove any YAML front-matter or trailing commas that would make JSON.parse return a non-object"],"exampleFix":"// before\nconst json = JSON.parse('[{\"name\":\"sales\"}]');\nvalidatePodTemplate(json); // throws\n\n// after\nconst json = JSON.parse('{\"name\":\"sales\", ...}');\nvalidatePodTemplate(json);","handlingStrategy":"type-guard","validationCode":"function isJsonObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n// Before calling validatePodTemplate:\nconst parsed = JSON.parse(raw);\nif (!isJsonObject(parsed)) {\n  console.error('Template must be a JSON object, got', typeof parsed);\n  process.exit(1);\n}","typeGuard":"function isJsonObject(v: unknown): v is Record<string, unknown> {\n  return typeof v === 'object' && v !== null && !Array.isArray(v);\n}","tryCatchPattern":"try {\n  const template = validatePodTemplate(parsed);\n} catch (e) {\n  if (e instanceof PodTemplateValidationError) {\n    console.error(`Validation failed at ${e.path}: ${e.message}`);\n  }\n}","preventionTips":["Always JSON.parse template files before validation — never pass raw strings","Use the business_pod_validate MCP tool to check templates before deployment","Start every pod-template file with '{' to guarantee object shape"],"tags":["pod-template","validation","schema","business-pods"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}