{"record":{"id":"43bfe3a7a9405189","repo":"ruvnet/ruflo","slug":"roomid-may-only-contain-a-za-z0-9-43bfe3","errorCode":null,"errorMessage":"roomId may only contain [A-Za-z0-9_.\\\\-:/@#]","messagePattern":"roomId may only contain \\[A-Za-z0-9_\\.\\\\\\\\-:/@#\\]","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-tools/agentbbs-tools.ts","lineNumber":83,"sourceCode":"  const abs = isAbsolute(p) ? p : resolve(getProjectCwd(), p);\n  return abs;\n}\n\nfunction validateRoomLabel(label: string): string {\n  if (!label || typeof label !== 'string') throw new Error('roomLabel is required');\n  if (label.length > 128) throw new Error('roomLabel exceeds 128 chars');\n  // Rooms are conventionally `#sales`, `#finance`, etc. — keep `#` in the allow-list.\n  if (!/^[A-Za-z0-9_.\\-:/@#]+$/.test(label)) {\n    throw new Error('roomLabel may only contain [A-Za-z0-9_.\\\\-:/@#]');\n  }\n  return label;\n}\n\nfunction validateRoomId(roomId: string): string {\n  if (!roomId || typeof roomId !== 'string') throw new Error('roomId is required');\n  if (roomId.length > 128) throw new Error('roomId exceeds 128 chars');\n  if (!/^[A-Za-z0-9_.\\-:/@#]+$/.test(roomId)) {\n    throw new Error('roomId may only contain [A-Za-z0-9_.\\\\-:/@#]');\n  }\n  return roomId;\n}\n\nfunction ensureDir(dir: string): void {\n  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n}\n\nfunction roomIdFromLabel(label: string): string {\n  // Stable, deterministic roomId — strip leading `#`, lowercase, and append a\n  // short hash so we don't collide across two rooms with the same canonical\n  // label but different policies. Phase 1: deterministic over (label).\n  const norm = label.replace(/^#/, '').toLowerCase();\n  const h = createHash('sha256').update(`agentbbs:room:${norm}`).digest('hex').slice(0, 8);\n  return `${norm}-${h}`;\n}\n\nfunction roomLogPath(basePath: string, roomId: string): string {","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/cli/src/mcp-tools/agentbbs-tools.ts#L65-L101","documentation":"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.","triggerScenarios":"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.","commonSituations":"Webhook handlers forwarding external ids verbatim; encodeURIComponent run over the id before the call; mixing agentbbs ids with other tool ecosystems' id formats.","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"],"exampleFix":"// before — percent-encoded id\nawait callMCPTool('agentbbs_read', { roomId: encodeURIComponent('#team room') }); // '%23team%20room'\n\n// after — plain allow-listed id\nawait callMCPTool('agentbbs_read', { roomId: '#team-room-1a2b3c' });","handlingStrategy":"validation","validationCode":"const ROOM_ID_RE = /^[A-Za-z0-9_.\\-:/@#]+$/;\n\nif (!ROOM_ID_RE.test(id)) {\n  throw new Error(`roomId has disallowed characters: ${JSON.stringify(id)}`);\n}","typeGuard":"const hasAllowedRoomIdCharset = (v: string): boolean => /^[A-Za-z0-9_.\\-:/@#]+$/.test(v);","tryCatchPattern":null,"preventionTips":["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"],"tags":["validation","agentbbs","charset","sanitization","rooms"],"backgroundTag":"disallowed-characters","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}