{"record":{"id":"15c8088fa2313e28","repo":"ruvnet/ruflo","slug":"payload-must-be-a-json-object","errorCode":null,"errorMessage":"payload must be a JSON object","messagePattern":"payload must be a JSON object","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-tools/agentbbs-tools.ts","lineNumber":403,"sourceCode":"          description: 'Event-specific JSON-serializable payload.',\n        },\n        signature: {\n          type: 'string',\n          description: 'Optional Ed25519 signature over the canonical envelope bytes. Phase 1: pass-through.',\n        },\n      },\n      required: ['roomId', 'msgType', 'payload'],\n    },\n    handler: async (input) => {\n      const basePath = resolveBasePath(input.basePath as string | undefined);\n      const roomId = validateRoomId(String(input.roomId));\n      const msgType = String(input.msgType ?? '');\n      if (!msgType) throw new Error('msgType is required');\n      if (msgType.length > 64 || !/^[A-Za-z0-9_-]+$/.test(msgType)) {\n        throw new Error('msgType must be alnum + _ - and ≤64 chars');\n      }\n      if (typeof input.payload !== 'object' || input.payload === null) {\n        throw new Error('payload must be a JSON object');\n      }\n\n      if (!agentbbsCliAvailable()) return degradedResult('agentbbs-not-found');\n\n      ensureDir(basePath);\n      const logPath = roomLogPath(basePath, roomId);\n      const base: BbsEnvelope = {\n        envelopeId: base64url(randomBytes(12)),\n        roomId,\n        seq: nextSeq(logPath),\n        msgType,\n        payload: input.payload,\n        timestamp: new Date().toISOString(),\n      };\n      // Phase 2: sign with this host's persistent node identity so peers can\n      // attribute and verify the envelope after a cross-host merge. An\n      // explicitly supplied signature is preserved rather than overwritten.\n      const env: BbsEnvelope = input.signature","sourceCodeStart":385,"sourceCodeEnd":421,"githubUrl":"https://github.com/ruvnet/ruflo/blob/9c61c86f06b439af2a95085ae9bb0ca839662e41/v3/@claude-flow/cli/src/mcp-tools/agentbbs-tools.ts#L385-L421","documentation":"The agentbbs post handler requires payload to be a JSON object (typeof 'object' and not null) because it becomes the BbsEnvelope's structured body that gets sequenced and persisted. Strings, numbers, booleans, and null throw; note that a JSON.stringify'd string is the most common offender — double-encoded payloads are rejected.","triggerScenarios":"Passing payload: JSON.stringify(data) instead of the object itself; payload: null or omitted (it is schema-required); payload as a bare string/number; arrays technically pass the typeof check but violate the object intent.","commonSituations":"Transport layers that serialize nested objects before dispatch (HTTP query strings, some IPC bridges); defensive 'stringify early' habits; copy-pasting curl examples where JSON was quoted.","solutions":["Pass the parsed object: payload: { text: 'hello' }, not payload: '\"{...}\"' or payload: '{\"text\":...}'","If your transport hands you a string, JSON.parse it before the call and send the object","Send arrays wrapped in an object ({ items: [...] }) to honor the object contract even though arrays slip past the check"],"exampleFix":"// before — double-encoded payload\nawait callMCPTool('agentbbs_post', { roomId, msgType: 'chat_message', payload: JSON.stringify({ text: 'hi' }) });\n\n// after — plain object\nawait callMCPTool('agentbbs_post', { roomId, msgType: 'chat_message', payload: { text: 'hi' } });","handlingStrategy":"validation","validationCode":"function toPayload(v: unknown): Record<string, unknown> {\n  if (typeof v === 'string') {\n    try { v = JSON.parse(v); } catch { throw new Error('payload is a non-JSON string — pass the object'); }\n  }\n  if (typeof v !== 'object' || v === null || Array.isArray(v)) {\n    throw new Error('payload must be a JSON object');\n  }\n  return v as Record<string, unknown>;\n}","typeGuard":"const isJsonObjectPayload = (v: unknown): v is Record<string, unknown> =>\n  typeof v === 'object' && v !== null && !Array.isArray(v);","tryCatchPattern":null,"preventionTips":["Never JSON.stringify the payload — pass the object","Wrap arrays as { items: [...] } to honor the object contract","Parse strings arriving from transports before dispatch"],"tags":["validation","agentbbs","payload","json","messaging"],"backgroundTag":"invalid-payload-type","analyzedSha":"9c61c86f06b439af2a95085ae9bb0ca839662e41","analyzedAt":"2026-09-15T22:58:14.805Z","contentChangedAt":"2026-09-15T22:58:14.805Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}