jquense/yup · error · Error

Unable to clone ${src}

Error message

Unable to clone ${src}

What it means

yup's cloneDeep (used when cloning schema option objects and values, e.g. default() / concat() / casting with structured cloning semantics) only knows how to clone plain objects, arrays, Date, RegExp, Map, Set and schemas. Anything else object-like that survives all instanceof checks (or non-Object exotic values reaching the else branch) hits `throw Error('Unable to clone ...')`.

Source

Thrown at src/util/cloneDeep.ts:40

    seen.set(src, copy);
    for (let i = 0; i < src.length; i++) copy[i] = clone(src[i], seen);
  } else if (src instanceof Map) {
    // Map
    copy = new Map();
    seen.set(src, copy);
    for (const [k, v] of src.entries()) copy.set(k, clone(v, seen));
  } else if (src instanceof Set) {
    // Set
    copy = new Set();
    seen.set(src, copy);
    for (const v of src) copy.add(clone(v, seen));
  } else if (src instanceof Object) {
    // Object
    copy = {};
    seen.set(src, copy);
    for (const [k, v] of Object.entries(src)) copy[k] = clone(v, seen);
  } else {
    throw Error(`Unable to clone ${src}`);
  }
  return copy;
}

export default clone;

View on GitHub (pinned to ff31eee8a2)

Solutions

  1. Replace the unclonable value with a plain serializable one, or provide it via a function: `default(() => getMyObj())` so yup clones the result at use-time — preferably a plain object.
  2. Convert the value before it reaches the schema (e.g. WeakMap→Map, DOM node→its id, class instance→plain object via toJSON/spread).
  3. If the value is a legit plain object failing instanceof due to cross-realm origins, normalize it with Object.assign({}, src) before use.
  4. Check recent changes: often a new default()/meta() input introduced the exotic value; log the value shown in the message and trace its origin.

Example fix

// before
const schema = yup.object().shape({
  ctx: yup.mixed().default(weakRefContext), // Unable to clone [object WeakMap]
});
// after
const schema = yup.object().shape({
  ctx: yup.mixed().default(() => ({ id: weakRefContext.id })),
});
Defensive patterns

Strategy: validation

Validate before calling

// ensure schema default/oneOf values are clonable before use
function isClonable(v: unknown): boolean {
  if (v == null || typeof v !== 'object') return true;
  if (v instanceof Date || v instanceof RegExp || Array.isArray(v) || v instanceof Map || v instanceof Set) return true;
  if (v.constructor === Object || v.constructor == null) return true;
  return false; // class instances, WeakMaps, DOM nodes, functions-in-containers are risky
}

Type guard

function isPlainObject(v: unknown): v is Record<string, unknown> {
  return typeof v === 'object' && v !== null &&
    (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);
}

Try / catch

try {
  const out = schema.cast(input);
} catch (e) {
  if (typeof e?.message === 'string' && e.message.startsWith('Unable to clone')) {
    console.error('Unclonable schema option/value detected:', e.message);
  }
}

Prevention

When it happens

Trigger: Passing an unclonable value into schema configuration that yup clones — e.g. `yup.string().default(() => someFunction)` is fine but `default(new WeakMap())`-style exotic objects, class instances that proxy weirdly, DOM nodes, or functions stored inside default/oneOf option objects; or circular structures in edge paths before the seen-map kicks in.

Common situations: Storing non-serializable objects (DOMNode, WeakMap, Symbol-keyed exotic, module namespace objects, worker/refs) in schema defaults or metadata; passing React refs or Vue reactive proxies into defaults; environment-specific objects (Buffer, cross-realm objects where instanceof fails).

Related errors


AI-assisted analysis of jquense/yup@ff31eee8a2 (2026-08-31). Data as JSON: /api/errors/936eb26bc10645d8. Report an issue: GitHub.