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

  1. Always pass payload as a plain object: `payload: { detail: 'x', code: 1 }`.
  2. If your data is a primitive, wrap it: `payload: { value: x }`.
  3. 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

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


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/15c8088fa2313e28. Report an issue: GitHub.