jackwener/OpenCLI · warning

[pipeline/template] sanitizeContext failed: ${err instanceof

Error message

[pipeline/template] sanitizeContext failed: ${err instanceof Error ? err.message : String(err)}. Returning {} for this branch. Likely cause: circular reference, Symbol, or other non-serializable value in pipeline context.

What it means

sanitizeContext serializes the pipeline context to plain JSON (handling BigInt) with a cache, so it can be safely passed into sandboxed template/JS evaluation. If JSON.stringify/parse fails — typically circular references, Symbols, functions, or other non-serializable values — the function warns and returns {} for this branch instead of throwing, so evaluation proceeds with an empty context.

Source

Thrown at src/pipeline/template.ts:222

const _sanitizeCache = new WeakMap<object, string>();

function sanitizeContext(obj: unknown): unknown {
  if (obj === null || obj === undefined) return obj;
  if (typeof obj !== 'object' && typeof obj !== 'function') return obj;
  const objRef = obj as object;
  const cached = _sanitizeCache.get(objRef);
  if (cached !== undefined) return JSON.parse(cached);
  try {
    // BigInt is non-serializable by default but is the most common cause of
    // sanitizeContext failures (e.g. GraphQL 64-bit IDs). Coerce to string
    // so callers see the value instead of a silent {}.
    const jsonStr = JSON.stringify(obj, (_key, value) =>
      typeof value === 'bigint' ? value.toString() : value,
    );
    _sanitizeCache.set(objRef, jsonStr);
    return JSON.parse(jsonStr);
  } catch (err) {
    log.warn(
      `[pipeline/template] sanitizeContext failed: ${err instanceof Error ? err.message : String(err)}. ` +
      `Returning {} for this branch. Likely cause: circular reference, Symbol, or other non-serializable value in pipeline context.`,
    );
    return {};
  }
}

/** LRU-bounded cache for compiled VM scripts — prevents unbounded memory growth. */
const MAX_VM_CACHE_SIZE = 256;
const _vmCache = new Map<string, vm.Script>();

function getOrCompileScript(expr: string): vm.Script {
  let script = _vmCache.get(expr);
  if (script) return script;

  // Evict oldest entry when cache is full
  if (_vmCache.size >= MAX_VM_CACHE_SIZE) {
    const firstKey = _vmCache.keys().next().value;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Find the circular/non-serializable value in the pipeline context and remove it or convert it to plain data (id strings instead of object references).
  2. Store only JSON-safe values in context: strings, numbers, booleans, arrays, plain objects.
  3. Wrap risky values with a toJSON() method returning a serializable representation.
  4. Note the branch will now receive {} — check whether downstream templates expected those fields and provide defaults.

Example fix

// before
context.parent = context; // circular
// after
context.parentId = 'root'; // store a reference key, not the object itself
Defensive patterns

Strategy: validation

Validate before calling

function isJsonSafe(value: unknown, seen = new Set()): boolean {
  if (value === null || typeof value !== 'object') return typeof value !== 'function' && typeof value !== 'symbol';
  if (seen.has(value)) return false;
  seen.add(value);
  return Object.values(value).every((v) => isJsonSafe(v, seen));
}
// before eval: if (!isJsonSafe(ctx)) console.warn('context has circular/non-serializable values');

Type guard

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

Try / catch

let safeCtx: Record<string, unknown> = {};
try {
  safeCtx = sanitizeContext(ctx);
} catch {
  safeCtx = {}; // branch will evaluate with empty context; log which keys were expected
}

Prevention

When it happens

Trigger: JSON.stringify throws or JSON.parse produces invalid state because obj contains a circular reference, a Symbol key/value, a function that toJSON turns into invalid JSON, or a getter that throws during serialization.

Common situations: Storing DOM nodes, page handles, or plugin objects in pipeline context; a BigInt left in a nested field (top-level is handled, nested replacer handles it too, but toJSON throwing is not); recursive data built by a previous pipeline step; adding class instances with circular back-references.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/faa2ae8bdf427e40. Report an issue: GitHub.