ruvnet/ruflo · error · PodTemplateValidationError
pod-template must be a JSON object
Error message
pod-template must be a JSON object
What it means
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.
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 fa13ee4ad6)
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
Example fix
// before
const raw = await readFile('pod.json', 'utf-8');
validatePodTemplate(raw); // raw is a string -> throws
// after
const raw = await readFile('pod.json', 'utf-8');
validatePodTemplate(JSON.parse(raw)); Defensive patterns
Strategy: type-guard
Validate before calling
const parsed = JSON.parse(raw);
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
if (Array.isArray(parsed)) { /* validate each element instead */ }
else throw new Error('pod template root must be a JSON object');
} Type guard
function isPlainObject(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v);
} Try / catch
try { validatePodTemplate(parsed); } catch (err) {
if (err instanceof PodTemplateValidationError && err.path === '/') {
// root shape wrong — check for double-encoded JSON or a list-of-pods file
if (typeof parsed === 'string') validatePodTemplate(JSON.parse(parsed));
}
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- bench.scheduleHours must be ≥1
- name must be lowercase-kebab (e.g. "sales")
- field "${key}" must be a non-empty string
- auditReadView must be an object
- includedEventTypes entries must be non-empty strings
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/2f4e3e61e5af856d.
Report an issue: GitHub.