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
- 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).
- Store only JSON-safe values in context: strings, numbers, booleans, arrays, plain objects.
- Wrap risky values with a toJSON() method returning a serializable representation.
- 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
- Store only JSON-safe primitives/plain objects in pipeline context.
- Replace object back-references with id strings.
- Give non-serializable values a toJSON() method.
- Don't leak DOM nodes/page handles into context from earlier steps.
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
- page.evaluate arguments must be JSON-serializable: ${describ
- page.evaluate arguments must be JSON-serializable
- 12306 ${endpoint} returned an unexpected payload shape
- ${label} returned a non-JSON response
- ${label} returned an unexpected payload shape; expected an o
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/faa2ae8bdf427e40.
Report an issue: GitHub.