ruvnet/ruflo · error · PodTemplateValidationError
pod-template at ${path}: pod-template must be a JSON object
Error message
pod-template at ${path}: pod-template must be a JSON object What it means
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'.
Source
Thrown at v3/@claude-flow/cli/src/business-pods/pod-schema.ts:189
}
// POSIX cron — five or six space-separated fields. Permissive on field
// contents (digits, *, -, /, ,) — actual cron evaluation happens at schedule
// time. We only catch obviously malformed values here.
const CRON_RE = /^([\d*/,\-]+\s+){4,5}[\d*/,\-]+$/;
/**
* Validate `json` and return a typed `PodTemplate`. Throws
* `PodTemplateValidationError` with a JSON-pointer-style path on failure.
*
* Used by:
* - `business_pod_validate` MCP tool — returns the error verbatim
* - `pod-tick.mjs` — pre-flight check before any pod execution
* - any external schema-loader that wants typed templates
*/
export function validatePodTemplate(json: unknown): PodTemplate {
if (!isObject(json)) {
throw new PodTemplateValidationError('pod-template must be a JSON object', '/');
}
const name = requireString(json, 'name', '/');
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
throw new PodTemplateValidationError('name must be lowercase-kebab (e.g. "sales")', '/');
}
const displayName = requireString(json, 'displayName', '/');
const roomId = requireString(json, 'roomId', '/');
if (!/^[A-Za-z0-9_.\-:/@#]+$/.test(roomId)) {
throw new PodTemplateValidationError(
'roomId may only contain [A-Za-z0-9_.\\-:/@#]',
'/',
);
}
const agents = requireArray(json, 'agents', '/', validatePodAgent);
if (agents.length === 0) {
throw new PodTemplateValidationError('agents must have ≥1 entry', '/');
}
const allowedMcpTools = requireArray(json, 'allowedMcpTools', '/', (t, tp) => {View on GitHub (pinned to 6b01dc5a68)
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
Example fix
// before
const json = JSON.parse('[{"name":"sales"}]');
validatePodTemplate(json); // throws
// after
const json = JSON.parse('{"name":"sales", ...}');
validatePodTemplate(json); Defensive patterns
Strategy: type-guard
Validate before calling
function isJsonObject(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v);
}
// Before calling validatePodTemplate:
const parsed = JSON.parse(raw);
if (!isJsonObject(parsed)) {
console.error('Template must be a JSON object, got', typeof parsed);
process.exit(1);
} Type guard
function isJsonObject(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v);
} Try / catch
try {
const template = validatePodTemplate(parsed);
} catch (e) {
if (e instanceof PodTemplateValidationError) {
console.error(`Validation failed at ${e.path}: ${e.message}`);
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- pod-template at ${path}: roomId may only contain [A-Za-z0-9_
- pod-template at ${path}: field "${key}" must be a non-empty
- pod-template at ${path}: field "${key}" must be a finite num
- pod-template at ${path}: field "${key}" must be a boolean
- pod-template at ${path}: field "${key}" must be an array
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/4a9c10150c5b0f7b.
Report an issue: GitHub.