ruvnet/ruflo · error

roomId has invalid characters

Error message

roomId has invalid characters

What it means

validateRoomId enforces a strict allow-list pattern (ROOM_ID_RE) on room IDs before any federation merge or sync touches them. The 'roomId has invalid characters' error means the supplied ID matched the length/required checks but contained characters outside the allowed set, preventing injection or malformed room keys from entering the federation layer.

Solutions

  1. Inspect the failing roomId and remove/rename characters not matching ROOM_ID_RE (stick to the allow-list, e.g. alphanumerics, '-', '_', '/', '.').
  2. Sanitize or re-encode the room ID at its source of creation so all rooms are created with valid IDs.
  3. Normalize input (trim whitespace, percent-decode URL fragments) before passing it to validateRoomId.
  4. Add a test with the exact rejected ID to confirm which characters violate the pattern.

Example fix

// before
const roomId = `rooms/${topic}:${channel}`;
validateRoomId(roomId); // ':' rejected -> 'roomId has invalid characters'
// after
const roomId = `rooms/${topic}-${channel.replace(/[^a-zA-Z0-9._/-]/g, '')}`;
validateRoomId(roomId);
Defensive patterns

Strategy: validation

Validate before calling

const ROOM_ID_SAFE = /^[a-zA-Z0-9._/-]{1,128}$/;
if (!ROOM_ID_SAFE.test(roomId)) throw new Error(`invalid roomId: ${JSON.stringify(roomId)}`);

Type guard

function isValidRoomId(v: unknown): v is string {
  return typeof v === 'string' && /^[a-zA-Z0-9._/-]{1,128}$/.test(v);
}

Try / catch

try {
  validateRoomId(roomId);
} catch (e) {
  if (e.message === 'roomId has invalid characters') {
    console.error(`Rejected roomId ${JSON.stringify(roomId)}: allowed chars only`);
    return { ok: false, reason: 'invalid-room-id' };
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling mergeEnvelopes, syncRoomFromPeer, or starting the server with a roomId that fails ROOM_ID_RE — e.g. one containing spaces, unicode, control characters, or symbols not permitted by the regex.

Common situations: Room IDs built by concatenating user input or URL fragments without sanitizing; IDs copied from logs with trailing whitespace; programmatic IDs generated with characters like ':' or '#' that the allow-list rejects.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of ruvnet/ruflo@2602b642d9 (2026-09-15). Data as JSON: /api/errors/d21554d169cfd212. Report an issue: GitHub.

Appendix: source

Thrown at v3/@claude-flow/cli/src/mcp-tools/agentbbs-federation.ts:321

}

export function readEnvelopes(basePath: string, roomId: string): SignedEnvelope[] {
  const p = roomLogPath(basePath, roomId);
  if (!existsSync(p)) return [];
  const out: SignedEnvelope[] = [];
  for (const line of readFileSync(p, 'utf-8').split(/\r?\n/)) {
    if (!line.trim()) continue;
    try { out.push(JSON.parse(line)); } catch { /* skip malformed */ }
  }
  return out;
}

export function validateRoomId(roomId: string): string {
  if (!roomId || typeof roomId !== 'string') throw new Error('roomId is required');
  if (roomId.length > 128) throw new Error('roomId exceeds 128 chars');
  // Also blocks path traversal: `.` is allowed but `/` segments cannot form
  // `..` without tripping the explicit check below.
  if (!ROOM_ID_RE.test(roomId)) throw new Error('roomId has invalid characters');
  if (roomId.includes('..')) throw new Error('roomId must not contain ..');
  return roomId;
}

export interface MergeResult {
  merged: number;
  skippedDuplicate: number;
  skippedUnverified: number;
  skippedOversize: number;
  skippedHopLimit: number;
}

/**
 * Union-merge verified envelopes from a peer into the local room log.
 *
 * Idempotent: `envelopeId` is the merge key, so replaying the same batch is a
 * no-op. Anything that fails verification is dropped and counted rather than
 * quarantined — a receiver has no use for an envelope it cannot attribute.

View on GitHub (pinned to 2602b642d9)