ruvnet/ruflo · error · Error

roomId may only contain [A-Za-z0-9_.\-:/@#]

Error message

roomId may only contain [A-Za-z0-9_.\-:/@#]

What it means

Thrown by validateRoomId() when roomId is present but fails the allow-list `/^[A-Za-z0-9_.\-:/@#]+$/` (length>128 is a separate throw). The allow-list matches validateRoomLabel so labels and ids accept the same safe symbol set; since roomId derives from a deterministic hash it should normally already be compliant, so hitting this usually means a caller hand-rolled a roomId instead of using register's output.

Source

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

  const abs = isAbsolute(p) ? p : resolve(getProjectCwd(), p);
  return abs;
}

function validateRoomLabel(label: string): string {
  if (!label || typeof label !== 'string') throw new Error('roomLabel is required');
  if (label.length > 128) throw new Error('roomLabel exceeds 128 chars');
  // Rooms are conventionally `#sales`, `#finance`, etc. — keep `#` in the allow-list.
  if (!/^[A-Za-z0-9_.\-:/@#]+$/.test(label)) {
    throw new Error('roomLabel may only contain [A-Za-z0-9_.\\-:/@#]');
  }
  return label;
}

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');
  if (!/^[A-Za-z0-9_.\-:/@#]+$/.test(roomId)) {
    throw new Error('roomId may only contain [A-Za-z0-9_.\\-:/@#]');
  }
  return roomId;
}

function ensureDir(dir: string): void {
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
}

function roomIdFromLabel(label: string): string {
  // Stable, deterministic roomId — strip leading `#`, lowercase, and append a
  // short hash so we don't collide across two rooms with the same canonical
  // label but different policies. Phase 1: deterministic over (label).
  const norm = label.replace(/^#/, '').toLowerCase();
  const h = createHash('sha256').update(`agentbbs:room:${norm}`).digest('hex').slice(0, 8);
  return `${norm}-${h}`;
}

function roomLogPath(basePath: string, roomId: string): string {

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Use the roomId returned by federation_bbs_register verbatim — it is always compliant.
  2. If you must construct one, restrict to alnum + `_ . - : / @ #` and trim whitespace first.
  3. Add a unit test that asserts every publish in your flow uses the register-derived id.

Example fix

// before — hand-rolled id
federation_bbs_publish({ roomId: 'sales room 1', msgType, payload });
// after
federation_bbs_publish({ roomId: reg.roomId, msgType, payload });
Defensive patterns

Strategy: validation

Validate before calling

const ROOM_ID_RE = /^[A-Za-z0-9_.\-:/@#]+$/;
function requireValidRoomId(id: string): string {
  if (!ROOM_ID_RE.test(id)) throw new Error('roomId has disallowed chars');
  return id;
}

Type guard

const isRoomId = (v: string): boolean => /^[A-Za-z0-9_.\-:/@#]+$/.test(v) && v.length <= 128;

Try / catch

null

Prevention

When it happens

Trigger: Passing a hand-constructed roomId containing a space, comma, or shell metacharacter; passing the roomLabel as roomId when the label itself contained a disallowed char; unicode/whitespace from copy-paste.

Common situations: Integrator built roomId from user input instead of from the register result; fixture with a placeholder like 'TODO'; a templating bug that injected whitespace.

Related errors


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