denoland/deno · error · std::io::Error

failed to deserialize npm process state: {}\n{}

Error message

failed to deserialize npm process state: {}\n{}

What it means

After reading the npm process state stream, it is parsed with serde_json::from_slice into NpmProcessState { kind, local_node_modules_path, linker_mode }. A parse failure is wrapped as InvalidData with the serde error plus the full lossy-UTF-8 contents of the buffer, so you can see exactly what bytes were read. It fires when the bytes are not the expected JSON — a foreign file, a truncated state file, or a format written by a different deno version.

Source

Thrown at libs/npm_installer/process_state.rs:153

    // seek to beginning. after the file is written the position will be inherited by this subprocess,
    // and also this file might have been read before
    file.seek(std::io::SeekFrom::Start(0))?;
    file.read_to_end(&mut buf).map_err(|err| {
      std::io::Error::new(
        err.kind(),
        format!(
          "failed to reading from {}: {}",
          match fd_or_path {
            FdOrPath::Fd(fd) => format!("fd {}", fd),
            FdOrPath::Path(path) => path.display().to_string(),
          },
          err,
        ),
      )
    })?;
    let state: NpmProcessState =
      serde_json::from_slice(&buf).map_err(|err| {
        std::io::Error::new(
          ErrorKind::InvalidData,
          format!(
            "failed to deserialize npm process state: {}\n{}",
            err,
            String::from_utf8_lossy(&buf)
          ),
        )
      })?;
    Ok(state)
  }

  pub fn as_serialized(&self) -> String {
    serde_json::to_string(self).unwrap()
  }
}

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Unset DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE_FD and let deno manage it — manual values are the top cause.
  2. Make the spawning parent and the spawned child use the same deno version so the serialized schema matches.
  3. If you produce the state file yourself, validate it is the exact JSON text deno wrote (the error prints the offending bytes) and that nothing rewrote it.

Example fix

# before
export DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE_FD=/tmp/state.json   # not deno's state file
deno run main.ts   # failed to deserialize npm process state: expected value at line 1 column 1

# after
unset DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE_FD
deno run main.ts
Defensive patterns

Strategy: validation

Validate before calling

// if you must inspect a state file before handing it to deno, parse it first
fn looks_like_npm_process_state(bytes: &[u8]) -> bool {
  let v: serde_json::Value = match serde_json::from_slice(bytes) {
    Ok(v) => v,
    Err(_) => return false,
  };
  v.get("kind").is_some()
}

Try / catch

match NpmProcessState::from_env_var(sys, env_value) {
  Ok(state) => { /* use state */ }
  Err(err) if err.kind() == std::io::ErrorKind::InvalidData
    && err.to_string().contains("deserialize npm process state") =>
  {
    // the env var points at a file that is not deno's state JSON — unset it and
    // let this process resolve npm deps on its own
    sys.env_remove_var("DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE_FD");
  }
  Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE_FD pointing at a file that is not a serialized NpmProcessState; the state file truncated mid-write; a parent deno of one version serializing state that a child of another version cannot parse (added/renamed fields like linker_mode).

Common situations: The env var exported manually or left over in CI pointing at an arbitrary file; deno upgraded on one side of a spawn (e.g. npx-installed deno child vs local parent); state file written to a tmp dir that a cleaner truncated; wrappers re-serializing or appending to the file.

Related errors


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