ruvnet/ruflo · error · Error

Unknown role: ${m.role}

Error message

Unknown role: ${m.role}

What it means

While mapping chat messages into WASM ChatMessageWasm objects, the switch statement only accepts roles 'system', 'user', and 'assistant'. Any other role string falls through to a default branch that throws, naming the offending role. This fires per-message during a messages.map(), so the throw aborts the whole batch and no template formatting happens.

Source

Thrown at v3/@claude-flow/cli/src/ruvector/ruvllm-wasm.ts:361

      phi: () => mod.ChatTemplateWasm.phi(),
      gemma: () => mod.ChatTemplateWasm.gemma(),
    };
    const factory = presets[template];
    if (!factory) throw new Error(`Unknown template preset: ${template}. Use: ${Object.keys(presets).join(', ')}`);
    tmpl = factory();
  } else if ('custom' in template) {
    tmpl = mod.ChatTemplateWasm.custom(template.custom);
  } else if ('modelId' in template) {
    tmpl = mod.ChatTemplateWasm.detectFromModelId(template.modelId);
  }

  // Build messages
  const wasmMessages = messages.map(m => {
    switch (m.role) {
      case 'system': return mod.ChatMessageWasm.system(m.content);
      case 'user': return mod.ChatMessageWasm.user(m.content);
      case 'assistant': return mod.ChatMessageWasm.assistant(m.content);
      default: throw new Error(`Unknown role: ${m.role}`);
    }
  });

  return tmpl.format(wasmMessages);
}

// ── KV Cache ─────────────────────────────────────────────────

/**
 * Create a KV cache for token management.
 */
export async function createKvCache(opts?: {
  tailLength?: number;
  maxTokens?: number;
  numKvHeads?: number;
  headDim?: number;
}): Promise<{
  append: (keys: Float32Array, values: Float32Array) => void;

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Pre-filter messages to only system/user/assistant before calling the formatter.
  2. Map or drop unsupported roles (e.g., collapse 'tool'/'function' messages into an 'assistant' message, or omit them).
  3. Validate the role field at your API boundary and reject early with a clear error naming the allowed set.

Example fix

// before
const out = formatChatMessages(rawOpenAIMessages); // throws on role:'tool'

// after: project to supported roles
const supported = new Set(['system', 'user', 'assistant']);
const safe = rawOpenAIMessages
  .filter(m => supported.has(m.role))
  .map(m => ({ role: m.role, content: m.content }));
const out = formatChatMessages(safe);
Defensive patterns

Strategy: validation

Validate before calling

const ALLOWED_ROLES = new Set(['system', 'user', 'assistant']);
function sanitizeMessages(messages) {
  const bad = messages.find(m => !ALLOWED_ROLES.has(m.role));
  if (bad) throw new Error(`Unsupported role '${bad.role}'. Allowed: system, user, assistant.`);
  return messages;
}

Type guard

type SupportedRole = 'system' | 'user' | 'assistant';
function isSupportedRole(r: string): r is SupportedRole {
  return r === 'system' || r === 'user' || r === 'assistant';
}

Prevention

When it happens

Trigger: Passing a message with role 'tool', 'function', 'developer' (OpenAI newer role), 'model' (Gemini naming), or 'system'/'user' with a typo like 'asistant'. Also forwarding raw provider payloads that include tool-call result messages.

Common situations: Translating OpenAI chat completions payloads (which include tool/function messages) into the WASM formatter; mixing Gemini 'model' role naming; user-supplied role strings from a config file.

Related errors


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/ee3452c4c28d6959. Report an issue: GitHub.