denoland/deno · critical

Attempted to read snapshot data out of range: {id} (of {})

Error message

Attempted to read snapshot data out of range: {id} (of {})

What it means

When a JsRuntime boots from a startup snapshot, load_snapshotted_data_from_snapshot rebuilds a SnapshotLoadDataStore, and extensions pull their snapshotted state out of it by index (SnapshotDataId, jsruntime.rs:1058+). get() panics "out of range" when an extension requests an id at or beyond the number of entries the snapshot actually stored. That means the extension/state layout at runtime no longer matches the layout that existed when the snapshot was created.

Source

Thrown at libs/core/runtime/snapshot.rs:73

  data.into_boxed_slice()
}

#[derive(Default)]
pub struct SnapshotLoadDataStore {
  data: Vec<Option<v8::Global<v8::Data>>>,
}

impl SnapshotLoadDataStore {
  pub fn get<'s, 'i, T>(
    &mut self,
    scope: &mut v8::PinScope<'s, 'i>,
    id: SnapshotDataId,
  ) -> v8::Global<T>
  where
    v8::Local<'s, T>: TryFrom<v8::Local<'s, v8::Data>>,
  {
    let Some(data) = self.data.get_mut(id as usize) else {
      panic!(
        "Attempted to read snapshot data out of range: {id} (of {})",
        self.data.len()
      );
    };
    let Some(data) = data.take() else {
      panic!("Attempted to read the snapshot data at index {id} twice");
    };
    let local = v8::Local::new(scope, data);
    let local = v8::Local::<T>::try_from(local).unwrap_or_else(|_| {
      panic!(
        "Invalid data type at index {id}, expected '{}'",
        std::any::type_name::<T>()
      )
    });
    v8::Global::new(scope, local)
  }
}

View on GitHub (pinned to f7822238ca)

Solutions

  1. Regenerate the startup snapshot so the stored state list matches the runtime's extension layout
  2. Make the extension vector (set and order) identical between create_snapshot options and RuntimeOptions at boot
  3. Wire create_snapshot's returned files into cargo:rerun-if-changed so the snapshot rebuilds whenever extension inputs change
  4. Pin deno_core and all extension crates to the same versions on both sides

Example fix

// before: snapshot built with [ext_a, ext_b], runtime boots [ext_a, ext_c, ext_b]
let rt = JsRuntime::new(RuntimeOptions {
  extensions: vec![ext_a(), ext_c(), ext_b()],
  startup_snapshot: Some(&SNAP),
  ..Default::default()
});

// after: same vector (order included) in build.rs and at boot
// build.rs: create_snapshot(CreateSnapshotOptions { extensions: vec![ext_a(), ext_c(), ext_b()], .. })
let rt = JsRuntime::new(RuntimeOptions {
  extensions: vec![ext_a(), ext_c(), ext_b()],
  startup_snapshot: Some(&REBUILT_SNAP),
  ..Default::default()
});
Defensive patterns

Strategy: validation

Validate before calling

// Stamp the extension layout into the snapshot artifact and check it before boot.
const SNAPSHOT_LAYOUT: &str = "ext-a:1,ext-c:2,ext-b:3"; // generated in build.rs
fn assert_layout(snapshot_tag: &str) {
  assert_eq!(snapshot_tag, SNAPSHOT_LAYOUT,
    "extension layout changed; regenerate the startup snapshot");
}

Prevention

When it happens

Trigger: Adding, removing, or reordering extensions that carry snapshot state between create_snapshot and runtime startup; loading a startup snapshot built by a different deno_core/deno_runtime version with a different state layout; concatenating or hand-editing snapshot blobs.

Common situations: Bumping deno_core or an extension crate version without regenerating the startup snapshot; feature flags that conditionally include an extension in the runtime but not in the snapshot build (or vice versa); CI caching a stale snapshot artifact.

Related errors


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