ruvnet/ruflo · error · TypeError
Unsupported federation signature mode: ${String(signatureMod
Error message
Unsupported federation signature mode: ${String(signatureMode)} What it means
Startup validation in the agent-federation plugin constructor: config.signatureMode (an EnvelopeSignatureMode) must be exactly 'legacy', 'prefer-jcs', or 'require-jcs'; anything else raises a TypeError immediately. When the key is absent the plugin falls back to DEFAULT_ENVELOPE_SIGNATURE_MODE, so this error always indicates an explicitly supplied bad value.
Source
Thrown at v3/@claude-flow/plugin-agent-federation/src/plugin.ts:103
private transport: LoadedTransport | null = null;
// A2A Agent Card well-known endpoint (opt-in via config.a2aCard). Null
// when the HTTP surface is off — everything else works without it.
private agentCardServer: AgentCardServerHandle | null = null;
async initialize(context: PluginContext): Promise<void> {
this.context = context;
const config = context.config;
const nodeId = (config['nodeId'] as string) ?? `node-${Date.now().toString(36)}`;
const endpoint = (config['endpoint'] as string) ?? 'ws://localhost:9100';
const complianceMode = (config['complianceMode'] as ComplianceMode) ?? 'none';
const staticPeers = (config['staticPeers'] as string[]) ?? [];
const hashSalt = (config['hashSalt'] as string) ?? `salt-${nodeId}`;
const signatureMode =
(config['signatureMode'] as EnvelopeSignatureMode | undefined)
?? DEFAULT_ENVELOPE_SIGNATURE_MODE;
if (!['legacy', 'prefer-jcs', 'require-jcs'].includes(signatureMode)) {
throw new TypeError(`Unsupported federation signature mode: ${String(signatureMode)}`);
}
// ADR-095 G2: real Ed25519 keypair instead of empty publicKey + stub
// signatures. Persist to .claude-flow/federation/key-<nodeId>.json so
// the same node identity survives restarts. Audit log
// audit_1776483149979 flagged the previous "verifySignature returns
// true unconditionally" as a critical authn bypass; this closes it.
const keyDir = join(process.cwd(), '.claude-flow', 'federation');
const keyPath = join(keyDir, `key-${nodeId}.json`);
let privateKey: Uint8Array;
let publicKeyHex: string;
try {
if (existsSync(keyPath)) {
const stored = JSON.parse(readFileSync(keyPath, 'utf-8')) as { privateKey: string; publicKey: string; nodeId: string };
privateKey = new Uint8Array(Buffer.from(stored.privateKey, 'hex'));
publicKeyHex = stored.publicKey;
} else {
privateKey = ed.utils.randomPrivateKey();View on GitHub (pinned to fa13ee4ad6)
Solutions
- Set signatureMode to one of the exact strings: 'legacy', 'prefer-jcs', or 'require-jcs'
- Omit the key entirely to use the built-in default mode
- Add a startup config validator that checks the enum before the plugin is constructed
Example fix
// before
const config = { signatureMode: 'jcs' }; // TypeError: Unsupported federation signature mode
// after
const config = { signatureMode: 'prefer-jcs' }; Defensive patterns
Strategy: type-guard
Validate before calling
const MODES = ['legacy', 'prefer-jcs', 'require-jcs'] as const;
const raw = config['signatureMode'];
if (raw !== undefined && !isEnvelopeSignatureMode(raw)) {
throw new TypeError(`Unsupported federation signature mode: ${String(raw)} (allowed: ${MODES.join(', ')})`);
} Type guard
const ENVELOPE_SIGNATURE_MODES = ['legacy', 'prefer-jcs', 'require-jcs'] as const;
type EnvelopeSignatureMode = (typeof ENVELOPE_SIGNATURE_MODES)[number];
function isEnvelopeSignatureMode(v: unknown): v is EnvelopeSignatureMode {
return typeof v === 'string' && (ENVELOPE_SIGNATURE_MODES as readonly string[]).includes(v);
} Try / catch
try {
host.register(plugin, config);
} catch (e) {
if (e instanceof TypeError && e.message.startsWith('Unsupported federation signature mode')) {
// fail deployment; surface the allowed list to the operator
} else throw e;
} Prevention
- Derive allowed values from one shared constant used by both config validation and docs
- Validate plugin config in CI before deploy
- Prefer omitting optional enum keys over copying values between environments
When it happens
Trigger: Passing signatureMode with a typo, wrong case, or invented value (e.g. 'jcs', 'Require-JCS', 'prefer_jcs'), or a config pipeline that coerces the value into a non-string (number, boolean, or nested object).
Common situations: Copying config snippets between plugin versions whose mode names changed; env-var mapping that injects the literal string 'undefined'; YAML/JSON config parsed into a nested object instead of a scalar string.
Related errors
- No models available to select task model
- OPENAI_BASE_URL not set
- Concurrent write detected on aggregate '${aggregateId}'. Res
- FederationBridge.decode: expected type 'claim-event', got '$
- FederationBridge.decode: payload missing or non-object
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/34eef628beb19b13.
Report an issue: GitHub.