ruvnet/ruflo · error · Error
roomId is required
Error message
roomId is required
What it means
Thrown by validateRoomId() when the supplied roomId is falsy, not a string, or empty. roomId is the canonical room identifier returned by federation_bbs_register and required by federation_bbs_publish / _watch. Unlike roomLabel it is the internal handle (deterministic hash), so it must be passed verbatim from the register result.
Source
Thrown at v3/@claude-flow/cli/src/mcp-tools/agentbbs-tools.ts:80
? input
: '.agentbbs';
if (/\.\.[\\/]|\0/.test(p)) throw new Error('basePath contains disallowed characters');
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}`;View on GitHub (pinned to 6b01dc5a68)
Solutions
- Always capture the `roomId` from the federation_bbs_register result and pass it unchanged to publish/watch.
- Validate the publish input has a non-empty roomId string at your boundary.
- If you only have a label, call register first to obtain the roomId.
Example fix
// before — forgot the register result
federation_bbs_publish({ msgType: 'alert', payload: {...} }); // throws
// after
const reg = await callMCPTool('federation_bbs_register', { roomLabel: '#sales' });
await callMCPTool('federation_bbs_publish', { roomId: reg.roomId, msgType: 'alert', payload: {...} }); Defensive patterns
Strategy: validation
Validate before calling
function requireRoomId(id: unknown): string {
if (typeof id !== 'string' || id.length === 0) {
throw new Error('roomId must be a non-empty string obtained from federation_bbs_register');
}
return id;
} Type guard
const isNonEmptyString = (v: unknown): v is string => typeof v === 'string' && v.length > 0;
Try / catch
null
Prevention
- Always thread the register result's roomId into publish/watch — never hand-roll it.
- Add an integration test that asserts publish uses the register-derived id.
- Type the publish input so a missing roomId is a compile error.
When it happens
Trigger: Calling federation_bbs_publish without roomId; passing the roomLabel where roomId was expected; the register step failed or its result was not captured before publish.
Common situations: Two-step flow where the caller forgot to thread the register result into publish; a refactor that renamed the field; a fixture that hard-coded an empty roomId placeholder.
Related errors
- roomLabel is required
- msgType is required
- roomLabel may only contain [A-Za-z0-9_.\-:/@#]
- roomId may only contain [A-Za-z0-9_.\-:/@#]
- msgType must be alnum + _ - and ≤64 chars
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/f6e59a6df07ed380.
Report an issue: GitHub.