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

  1. Replace numeric/null entries with the actual MCP tool name strings
  2. Filter out falsy entries: allowedMcpTools = allowedMcpTools.filter(t => typeof t === 'string' && t.length > 0)
  3. 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

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


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/4af4819278c7e7a5. Report an issue: GitHub.