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
validateRoomId() enforces the same allow-list charset as roomLabel — A-Za-z0-9 and _.\-:/@# — on room identifiers. Derived ids always satisfy it (lowercased label + base hash), so a violation means a foreign id format (spaces, parentheses, unicode, percent-encoding) was passed in.
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 fa13ee4ad6)
Solutions
- Use tool-returned roomIds verbatim — do not re-encode or decorate them
- Strip/replace disallowed characters if you must map external ids: encodeURIComponent is the wrong direction, decode instead
- Validate with the same regex /^[A-Za-z0-9_.\-:/@#]+$/ at your boundary for an early, contextual error
Example fix
// before — percent-encoded id
await callMCPTool('agentbbs_read', { roomId: encodeURIComponent('#team room') }); // '%23team%20room'
// after — plain allow-listed id
await callMCPTool('agentbbs_read', { roomId: '#team-room-1a2b3c' }); Defensive patterns
Strategy: validation
Validate before calling
const ROOM_ID_RE = /^[A-Za-z0-9_.\-:/@#]+$/;
if (!ROOM_ID_RE.test(id)) {
throw new Error(`roomId has disallowed characters: ${JSON.stringify(id)}`);
} Type guard
const hasAllowedRoomIdCharset = (v: string): boolean => /^[A-Za-z0-9_.\-:/@#]+$/.test(v);
Prevention
- Pass tool-returned ids verbatim — no re-encoding or decoration
- Decode (not encode) URL-escaped ids before use
- Validate with the same regex when bridging external id schemes
When it happens
Trigger: URL-encoded ids containing %20; ids with parentheses or brackets from template strings; unicode from user input; ids copied from a different system with its own escaping scheme.
Common situations: Webhook handlers forwarding external ids verbatim; encodeURIComponent run over the id before the call; mixing agentbbs ids with other tool ecosystems' id formats.
Related errors
- roomLabel may only contain [A-Za-z0-9_.\\-:/@#]
- basePath contains disallowed characters
- roomLabel is required
- roomLabel exceeds 128 chars
- roomId is required
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/43bfe3a7a9405189.
Report an issue: GitHub.