danny-avila/LibreChat · error · AgentRunEnvelopeError

${path} contains a non-plain ${typeName} value

Error message

${path} contains a non-plain ${typeName} value

What it means

Thrown by cloneJsonValue for an object whose prototype is neither Object.prototype nor null. Only plain objects (object literals, Object.create(null)) are accepted; class instances, Date, Map, Set, Error, RegExp, and typed arrays are rejected. These carry behavior and hidden slots that do not survive JSON, so the envelope requires the caller to flatten them to plain data explicitly.

Source

Thrown at packages/api/src/agents/envelope.ts:167

        }
        const descriptor = Object.getOwnPropertyDescriptor(value, key);
        if (!descriptor || !Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
          throw new AgentRunEnvelopeError(`${path}[${index}] must not be an accessor property`);
        }
        const itemValue: unknown = descriptor.value;
        cloned[index] = cloneJsonValue(itemValue, `${path}[${index}]`, ancestors, depth + 1);
        clonedItemCount++;
      }
      if (clonedItemCount !== value.length) {
        throw new AgentRunEnvelopeError(`${path} contains sparse array entries`);
      }
      return cloned;
    }

    const prototype = Object.getPrototypeOf(value);
    if (prototype !== Object.prototype && prototype !== null) {
      const typeName = value.constructor?.name ?? 'object';
      throw new AgentRunEnvelopeError(`${path} contains a non-plain ${typeName} value`);
    }

    const cloned: { [key: string]: unknown } = {};
    for (const key of Object.getOwnPropertyNames(value)) {
      const descriptor = Object.getOwnPropertyDescriptor(value, key);
      if (descriptor?.enumerable !== true) {
        throw new AgentRunEnvelopeError(`${path}.${key} must be an enumerable property`);
      }
      if (!Object.prototype.hasOwnProperty.call(descriptor, 'value')) {
        throw new AgentRunEnvelopeError(`${path}.${key} must not be an accessor property`);
      }
      const propertyValue: unknown = descriptor.value;
      Object.defineProperty(cloned, key, {
        configurable: true,
        enumerable: true,
        writable: true,
        value: cloneJsonValue(propertyValue, `${path}.${key}`, ancestors, depth + 1),
      });

View on GitHub (pinned to 5ff282f900)

Solutions

  1. Convert class instances to plain literals before insertion: date.toISOString(), [...map.entries()], err.message, buf.toString('base64').
  2. Use JSON.parse(JSON.stringify(obj)) to collapse to a plain object when you know the shape is JSON-safe.
  3. Build payload objects as literal {...} or Object.assign({}, instance) of only the data fields.

Example fix

// before
const payload = { createdAt: new Date(), tags: new Set(['a','b']) };
createAgentRunEnvelope({ protocol, requestId, receivedAt, principal, payload });

// after
const payload = { createdAt: new Date().toISOString(), tags: [...new Set(['a','b'])] };
createAgentRunEnvelope({ protocol, requestId, receivedAt, principal, payload });
Defensive patterns

Strategy: type-guard

Validate before calling

function isPlainObjectOrArray(value: unknown): boolean {
  if (value === null || typeof value !== 'object') return true;
  const proto = Object.getPrototypeOf(value);
  if (Array.isArray(value)) return Object.getPrototypeOf(value) === Array.prototype;
  return proto === Object.prototype || proto === null;
}
function toPlainDeep(value: unknown): unknown {
  if (Array.isArray(value)) return value.map(toPlainDeep);
  if (value && typeof value === 'object') {
    if (value instanceof Date) return value.toISOString();
    if (value instanceof Map) return toPlainDeep(Object.fromEntries(value));
    if (value instanceof Set) return toPlainDeep([...value]);
    const out: Record<string, unknown> = {};
    for (const [k, v] of Object.entries(value)) out[k] = toPlainDeep(v);
    return out;
  }
  return value;
}

Type guard

function isPlainJsonValue(value: unknown, seen = new WeakSet()): boolean {
  if (value === null || typeof value !== 'object') return true;
  if (seen.has(value as object)) return true;
  seen.add(value as object);
  const proto = Object.getPrototypeOf(value);
  if (Array.isArray(value)) return value.every((v) => isPlainJsonValue(v, seen));
  if (proto !== Object.prototype && proto !== null) return false;
  return Object.values(value).every((v) => isPlainJsonValue(v, seen));
}

Try / catch

try {
  const env = createAgentRunEnvelope(input);
} catch (e) {
  if (e instanceof AgentRunEnvelopeError && /non-plain/.test(e.message)) {
    input.payload = JSON.parse(JSON.stringify(input.payload)) as typeof input.payload;
  } else throw e;
}

Prevention

When it happens

Trigger: Passing a Date, Map, Set, Error, RegExp, Uint8Array, URL, or any class instance (new Foo()) inside the payload. Also a plain object whose __proto__ was reassigned to a non-default prototype.

Common situations: Dropping an ORM model instance, a Date timestamp, or a URL object into the request payload instead of its primitive form; passing Error objects caught in a handler onward as payload.

Related errors


AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12). Data as JSON: /api/errors/4d0252b147531254. Report an issue: GitHub.