ruvnet/ruflo · error · PodTemplateValidationError

name must be lowercase-kebab (e.g. "sales")

Error message

name must be lowercase-kebab (e.g. "sales")

What it means

The template's top-level name must match /^[a-z][a-z0-9-]*$/: start with a lowercase letter, followed only by lowercase letters, digits, and hyphens. Uppercase letters, underscores, leading digits/hyphens, spaces, and punctuation are rejected. The kebab name is the pod's stable identifier (used in scheduling, logs, and MCP tooling).

Source

Thrown at v3/@claude-flow/cli/src/business-pods/pod-schema.ts:193

// 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) => {
    if (typeof t !== 'string' || t.length === 0) {
      throw new PodTemplateValidationError(
        'allowedMcpTools entries must be non-empty strings',
        tp,

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Rewrite name in lowercase-kebab: "sales", "growth-eng", "support-tier1"
  2. Keep the human label in displayName — that field has no format restriction
  3. Verify with a quick regex test before submitting the template to business_pod_validate

Example fix

// before
{ "name": "Sales_Pod", "displayName": "Sales" }
// after
{ "name": "sales-pod", "displayName": "Sales" }
Defensive patterns

Strategy: validation

Validate before calling

if (!/^[a-z][a-z0-9-]*$/.test(template.name)) {
  template.name = template.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+/, '');
}

Type guard

function isKebabName(v: unknown): v is string {
  return typeof v === 'string' && /^[a-z][a-z0-9-]*$/.test(v);
}

Try / catch

try { validatePodTemplate(json); } catch (err) {
  if (err instanceof PodTemplateValidationError && /lowercase-kebab/.test(err.message)) {
    // slugify name, keep the original in displayName, retry
  }
}

Prevention

When it happens

Trigger: validatePodTemplate() on a template with name values like "Sales", "sales_pod", "1sales", "-sales", or "sales pod". The error is reported at path '/'.

Common situations: Copying the human-facing displayName ("Sales Pod") into name; org naming conventions with underscores; title-cased department names pasted from docs.

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/076887bc6edd9d4a. Report an issue: GitHub.