ruvnet/ruflo · error · Error
payload must be a JSON object
Error message
payload must be a JSON object
What it means
Thrown by the federation_bbs_publish handler when input.payload is not a plain JSON object — i.e. typeof !== 'object', or it is null. Payload is wrapped into the ReplicateMessage envelope as-is and appended to the room log, so it must be a JSON-serializable record; arrays, primitives, and undefined are rejected because they break the envelope contract and the cockpit's typed dispatch.
Source
Thrown at v3/@claude-flow/cli/src/mcp-tools/agentbbs-tools.ts:280
description: 'Event-specific JSON-serializable payload.',
},
signature: {
type: 'string',
description: 'Optional Ed25519 signature over the canonical envelope bytes. Phase 1: pass-through.',
},
},
required: ['roomId', 'msgType', 'payload'],
},
handler: async (input) => {
const basePath = resolveBasePath(input.basePath as string | undefined);
const roomId = validateRoomId(String(input.roomId));
const msgType = String(input.msgType ?? '');
if (!msgType) throw new Error('msgType is required');
if (msgType.length > 64 || !/^[A-Za-z0-9_-]+$/.test(msgType)) {
throw new Error('msgType must be alnum + _ - and ≤64 chars');
}
if (typeof input.payload !== 'object' || input.payload === null) {
throw new Error('payload must be a JSON object');
}
const api = await loadAgentbbs();
if (!api) return degradedResult('agentbbs-not-found');
ensureDir(basePath);
const logPath = roomLogPath(basePath, roomId);
const env: BbsEnvelope = {
envelopeId: base64url(randomBytes(12)),
roomId,
seq: nextSeq(logPath),
msgType,
payload: input.payload,
timestamp: new Date().toISOString(),
signature: input.signature ? String(input.signature) : undefined,
};
appendFileSync(logPath, JSON.stringify(env) + '\n');
View on GitHub (pinned to 6b01dc5a68)
Solutions
- Always pass payload as a plain object: `payload: { detail: 'x', code: 1 }`.
- If your data is a primitive, wrap it: `payload: { value: x }`.
- Guard at your boundary: `if (typeof p !== 'object' || p === null) throw ...` before calling the tool.
Example fix
// before
federation_bbs_publish({ roomId, msgType, payload: 'disk full' });
// after
federation_bbs_publish({ roomId, msgType, payload: { message: 'disk full', code: 'ENOSPC' } }); Defensive patterns
Strategy: type-guard
Validate before calling
function requirePayloadObject(p: unknown): Record<string, unknown> {
if (typeof p !== 'object' || p === null || Array.isArray(p)) {
throw new Error('payload must be a plain JSON object');
}
return p as Record<string, unknown>;
} Type guard
const isPlainObject = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
Try / catch
null
Prevention
- Wrap primitive payloads in an object before publish.
- Double-check you are not passing JSON.stringify output as payload.
- Schema-validate payload at your boundary (zod object with passthrough).
When it happens
Trigger: Passing payload as a string, number, array, or undefined; passing null explicitly; a JSON.parse(...) that returned a primitive; a serializer that returned undefined under an error path.
Common situations: Producer that sometimes emits a bare string instead of an object; a fixture with `payload: JSON.stringify(x)` (double-encoded); a default {} that got overwritten to null upstream.
Related errors
- roomLabel is required
- roomLabel may only contain [A-Za-z0-9_.\-:/@#]
- roomId is required
- roomId may only contain [A-Za-z0-9_.\-:/@#]
- msgType is required
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/15c8088fa2313e28.
Report an issue: GitHub.