danny-avila/LibreChat · error · AgentRunEnvelopeError

${path}.${key} must be an enumerable property

Error message

${path}.${key} must be an enumerable property

What it means

Thrown by cloneJsonValue when a plain object's own property descriptor has enumerable !== true. Object.getOwnPropertyNames returns non-enumerable keys too, so properties defined with enumerable:false (or hidden flags) are caught. JSON.stringify skips non-enumerable keys, which would silently drop data; the envelope clone surfaces the loss as an error.

Source

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

        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),
      });
    }
    return cloned;
  } finally {
    ancestors.delete(value);
  }
}

View on GitHub (pinned to 5ff282f900)

Solutions

  1. Recreate the object as a literal containing only the fields you need, copied by name.
  2. Redefine the property as enumerable: Object.defineProperty(obj, key, { enumerable: true }).
  3. Use Object.entries / a manual pick list (which only see enumerable props) to rebuild a clean plain object.

Example fix

// before
Object.defineProperty(cfg, 'secret', { value: 'x', enumerable: false });
createAgentRunEnvelope({ protocol, requestId, receivedAt, principal, payload: { cfg } });

// after
const cfg = { ...{ secret: 'x' } }; // literal, enumerable by default
createAgentRunEnvelope({ protocol, requestId, receivedAt, principal, payload: { cfg } });
Defensive patterns

Strategy: validation

Validate before calling

function allOwnPropsEnumerable(obj: Record<string, unknown>): boolean {
  return Object.getOwnPropertyNames(obj).every((k) =>
    Object.getOwnPropertyDescriptor(obj, k)?.enumerable === true);
}
function toEnumerablePlain<T extends Record<string, unknown>>(obj: T): Record<string, unknown> {
  const out: Record<string, unknown> = {};
  for (const [k, v] of Object.entries(obj)) out[k] = v; // entries sees only enumerable
  return out;
}

Type guard

function isAllEnumerable(obj: Record<PropertyKey, unknown>): boolean {
  return Object.getOwnPropertyNames(obj).every(
    (k) => Object.getOwnPropertyDescriptor(obj, k)?.enumerable === true,
  );
}

Try / catch

try {
  const env = createAgentRunEnvelope(input);
} catch (e) {
  if (e instanceof AgentRunEnvelopeError && /enumerable property/.test(e.message)) {
    input.payload = JSON.parse(JSON.stringify(input.payload)) as typeof input.payload;
    // JSON round-trip drops non-enumerable keys; confirm that is acceptable
  } else throw e;
}

Prevention

When it happens

Trigger: Object.defineProperty(obj, 'id', { value: 1, enumerable: false }), or libraries that define config fields as non-enumerable. Also class fields declared as non-enumerable via decorators.

Common situations: Using defineProperty for 'private' fields that you later need in the payload; objects returned from Object.assign over a frozen/non-enumerable template; ORM hidden columns flagged non-enumerable.

Related errors


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