denoland/deno · critical

unable to convert

Error message

unable to convert

What it means

The second failure mode of bindings::get(): the property at the given path exists, but converting the v8 value to the expected Rust type (v8::Object, v8::Function, etc. via TryInto) fails, so the "unable to convert" panic fires. This means the global shape is present but wrong: something replaced a binding with a value of a different type before deno_core grabbed it. Typical sources are user/middleware JavaScript overwriting internals, or a snapshot from an incompatible deno_core version where the path holds a different kind of value.

Source

Thrown at libs/core/runtime/bindings.rs:316

    )
  }
}

pub(crate) fn get<'s, 'i, T>(
  scope: &mut v8::PinScope<'s, 'i>,
  from: v8::Local<'s, v8::Object>,
  key: FastStaticString,
  path: &'static str,
) -> T
where
  v8::Local<'s, v8::Value>: TryInto<T>,
{
  let key = key.v8_string(scope).unwrap();
  from
    .get(scope, key.into())
    .unwrap_or_else(|| panic!("{path} exists"))
    .try_into()
    .unwrap_or_else(|_| panic!("unable to convert"))
}

/// Create an object on the `globalThis` that looks like this:
/// ```ignore
/// globalThis.Deno = {
///   core: {
///     ops: {},
///   },
///   // console from V8
///   console,
///   // wrapper fn to forward message to V8 console
///   callConsole,
/// };
/// ```
pub(crate) fn initialize_deno_core_namespace<'s, 'i>(
  scope: &mut v8::PinScope<'s, 'i>,
  context: v8::Local<'s, v8::Context>,
  init_mode: InitMode,

View on GitHub (pinned to 336da420f4)

Solutions

  1. Find the code that overwrites the binding and make it extend the existing object instead of replacing it
  2. Regenerate the startup snapshot with the same deno_core version as the binary so every binding has the expected type
  3. Audit global_object_middlewares for code that runs before init and returns/sets different types
  4. If patching is required, do it after JsRuntime initialization completes, not during startup

Example fix

// before (startup script runs before core grabs bindings):
globalThis.Deno = { core: { ops: 'shim' } }; // replaces real objects with a string

// after: extend, never replace
const deno = globalThis.Deno;
globalThis.Deno = { ...deno, extraFlag: true };
Defensive patterns

Strategy: validation

Validate before calling

// Guard in bootstrap scripts: verify the binding shape before touching it.
if (typeof globalThis.Deno?.core?.ops !== 'object' || globalThis.Deno.core.ops === null) {
  throw new Error('Deno.core.ops missing or wrong type; refusing to patch');
}

Type guard

// TypeScript: narrow before patching internals
function hasDenoCore(v: unknown): v is { core: { ops: Record<string, unknown> } } {
  return !!v && typeof v === 'object'
    && 'core' in v && typeof (v as any).core === 'object'
    && 'ops' in (v as any).core && typeof (v as any).core.ops === 'object';
}

Prevention

When it happens

Trigger: A startup script or global_object_middleware assigning a non-object to globalThis.Deno.core (e.g. a string or plain shim) before runtime init; a snapshot built by a deno_core version where an expected Function binding (event loop tick, timers, error constructors) was an Object or undefined; JS running between context creation and bindings::get that mutates Deno internals.

Common situations: Embedders monkey-patching Deno.core in bootstrap code; snapshot/binary version skew after a dependency bump; polyfill libraries loaded eagerly that replace built-ins wholesale instead of extending them.

Related errors


AI-assisted analysis of denoland/deno@336da420f4 (2026-08-20). Data as JSON: /api/errors/d1cfb0d41c67d118. Report an issue: GitHub.