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
- Use the roomId returned by federation_bbs_register verbatim — it is always compliant.
- If you must construct one, restrict to alnum + `_ . - : / @ #` and trim whitespace first.
- 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
- Use register's output verbatim rather than constructing roomId yourself.
- If you must construct, mirror the allow-list and length cap exactly.
- Add a property test that random roomId outputs from register always validate.
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
- roomLabel may only contain [A-Za-z0-9_.\-:/@#]
- roomLabel is required
- roomId is required
- msgType is required
- msgType must be alnum + _ - and ≤64 chars
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/4b1fc3c4842ca439.
Report an issue: GitHub.