ruvnet/ruflo · error · PodTemplateValidationError
allowedMcpTools entries must be non-empty strings
Error message
allowedMcpTools entries must be non-empty strings
What it means
Each entry of the top-level allowedMcpTools array must be a non-empty string — the tool names the pod agent may invoke (an allowlist). Numbers, null, booleans, and "" are rejected at the entry's path (/allowedMcpTools/<index>). Note "*" is accepted as a regular non-empty string; the schema does not special-case it here.
Source
Thrown at v3/@claude-flow/cli/src/business-pods/pod-schema.ts:209
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) => {
if (typeof t !== 'string' || t.length === 0) {
throw new PodTemplateValidationError(
'allowedMcpTools entries must be non-empty strings',
tp,
);
}
return t;
});
if (allowedMcpTools.length === 0) {
throw new PodTemplateValidationError('allowedMcpTools must have ≥1 entry', '/');
}
const bench = validatePodBench(json.bench, '/bench');
const piiPolicy = requireString(json, 'piiPolicy', '/');
if (!PII_POLICIES.includes(piiPolicy as PiiPolicy)) {
throw new PodTemplateValidationError(
`piiPolicy must be one of: ${PII_POLICIES.join(', ')}`,
'/',
);
}
const budgetUsdMonthly = requireNumber(json, 'budgetUsdMonthly', '/');View on GitHub (pinned to fa13ee4ad6)
Solutions
- Replace numeric/null entries with the actual MCP tool name strings
- Filter out falsy entries: allowedMcpTools = allowedMcpTools.filter(t => typeof t === 'string' && t.length > 0)
- Add a template unit test that asserts every entry is a non-empty string before validation
Example fix
// before "allowedMcpTools": ["memory_store", null, ""] // after "allowedMcpTools": ["memory_store", "task_create"]
Defensive patterns
Strategy: validation
Validate before calling
json.allowedMcpTools = (json.allowedMcpTools as unknown[]) .filter((t): t is string => typeof t === 'string' && t.length > 0);
Type guard
function isNonEmptyStringArray(v: unknown): v is string[] {
return Array.isArray(v) && v.every(e => typeof e === 'string' && e.length > 0);
} Try / catch
try { validatePodTemplate(json); } catch (err) {
if (err instanceof PodTemplateValidationError && err.path.startsWith('/allowedMcpTools/')) {
// drop or fix the offending entry at the index in err.path, retry
}
} Prevention
- Never build tool lists with Array(n).map — use explicit literals or filtered maps
- Assert entries are strings in the generator's own unit tests
When it happens
Trigger: A template with allowedMcpTools containing 0 or other numbers (e.g. from enum indices), null holes, or empty strings produced by join/split bugs in template generation scripts.
Common situations: Programmatic template builders mapping tool IDs to indices; arrays built with Array(n).map(...) leaving undefined/null; trailing empty entries from splitting a delimited string on a trailing delimiter.
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
- allowedMcpTools must have ≥1 entry
- bench.scheduleHours must be ≥1
- auditReadView must be an object
- includedEventTypes entries must be non-empty strings
- auditReadView.retentionDays must be ≥1
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/4af4819278c7e7a5.
Report an issue: GitHub.