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

Unexpected end of data

Error message

Unexpected end of data

What it means

Parsing a deno compile binary's trailing metadata uses length-prefixed byte reads guarded by check_has_len(): every read demands that `len` bytes remain in the input. When the remaining slice is shorter than the declared length, parsing stops with io::ErrorKind::InvalidData 'Unexpected end of data' — the metadata section is truncated relative to its own length fields.

Source

Thrown at cli/lib/standalone/binary.rs:423

  let len = u32::from_le_bytes(len_bytes.try_into().unwrap());
  Ok((input, len))
}

fn read_u8(input: &[u8]) -> std::io::Result<(&[u8], u8)> {
  check_has_len(input, 1)?;
  Ok((&input[1..], input[0]))
}

fn read_bytes(input: &[u8], len: usize) -> std::io::Result<(&[u8], &[u8])> {
  check_has_len(input, len)?;
  let (len_bytes, input) = input.split_at(len);
  Ok((input, len_bytes))
}

#[inline(always)]
fn check_has_len(input: &[u8], len: usize) -> std::io::Result<()> {
  if input.len() < len {
    Err(std::io::Error::new(
      std::io::ErrorKind::InvalidData,
      "Unexpected end of data",
    ))
  } else {
    Ok(())
  }
}

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Rebuild the binary from source with an unchanged toolchain and verify it runs on the build machine before shipping
  2. Compare file sizes/checksums between build and deploy locations to detect truncation
  3. Avoid post-processing compiled binaries with packers/signers that rewrite the file layout; Deno's trailer must remain intact
  4. Ensure the compile process fully succeeds (exit code 0, no disk-full errors) before distributing

Example fix

# before
# artifact truncated in transfer
deno run ./app  # Unexpected end of data

# after
# rebuild and verify checksum end-to-end
deno compile -o app src/mod.ts && sha256sum app
scp app host:&& ssh host sha256sum app  # must match; then run
Defensive patterns

Strategy: validation

Validate before calling

// Refuse to parse obviously-truncated artifacts
const EXPECTED_MIN = 1024; // whatever your pipeline records at build time
const st = await Deno.stat(binaryPath);
if (st.size < EXPECTED_MIN) throw new Error(`Binary looks truncated: ${st.size} < ${EXPECTED_MIN}`);
const bytes = await Deno.readFile(binaryPath);
if (bytes.at(-1) === undefined || bytes.length === 0) throw new Error("empty binary");

Try / catch

// Rust: parse the standalone trailer defensively
match parse_binary_trailer(&bytes) {
    Err(e) if e.kind() == std::io::ErrorKind::InvalidData && e.to_string().contains("Unexpected end of data") => {
        // re-download/rebuild; parsing again on the same bytes cannot succeed
        Err(anyhow!("compiled binary truncated; expected {} bytes", expected_len))
    }
    r => r.map_err(Into::into),
}

Prevention

When it happens

Trigger: A compiled binary truncated by a failed write (disk full, killed process) or partial transfer, so the trailer's declared section lengths exceed the actual bytes; a binary whose bytes were appended-to/repacked with wrong offsets; version-skewed writers emitting lengths a reader interprets differently.

Common situations: `deno compile` output copied with cp/rsync interrupted; docker COPY of a still-being-written file; CI artifact upload truncated; binaries sent over channels that strip trailing bytes (rare) or modified by post-processing tools (upx, signtool) that break Deno's trailer layout.

Related errors


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