Pumpkin-MC/Pumpkin · info · ReadingError

EOF, Tried to read but No bytes left to consume

Error message

EOF, Tried to read {0} but No bytes left to consume

What it means

ReadingError::CleanEOF is produced by the protocol's async reader when it attempts to read a field of the given size but the stream has ended with no bytes left to consume. It is the serde-style deserializer's way of reporting a clean end-of-stream: the peer closed the connection mid-decode expectation, with zero bytes available for the requested read.

Solutions

  1. Treat CleanEOF at packet boundaries as a normal disconnect: log at debug level and close the connection gracefully.
  2. Check for stream closure (read returning Ok(0)) before attempting to decode the next packet.
  3. Do not retry reads on a closed stream; re-establish the connection if communication must continue.
  4. If it happens mid-packet rather than at a boundary, check whether the sender truncates packets (see Incomplete).

Example fix

// before: logging every EOF as an error and spamming logs
error!("read failed: {e}");

// after: distinguish clean disconnects
match e {
    ReadingError::CleanEOF(_) => debug!("client disconnected"),
    other => error!("read failed: {other}"),
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check stream liveness before a read loop iteration
if stream.peek(&mut [0u8; 1]).await?.is_empty() { break; } // peer closed

Try / catch

match reader.next_packet().await {
    Err(ReadingError::CleanEOF(_)) => {
        debug!("peer closed connection cleanly");
        break; // normal disconnect path
    }
    Err(e) => warn!("read error: {e}"),
    Ok(p) => handle(p),
}

Prevention

When it happens

Trigger: Calling any read/deserialize step (e.g. reading a VarInt, length prefix, or fixed-size buffer) on a TcpStream whose peer has closed; the reader tries to fill the requested byte count from an exhausted reader.

Common situations: A Minecraft client disconnecting right as the server awaits the next packet; a keep-alive/poll loop reading a closed socket; reading from a half-closed connection after a client crash or timeout on the peer side.

Related errors


AI-assisted analysis of Pumpkin-MC/Pumpkin@8d4639e25a (2026-09-09). Data as JSON: /api/errors/77cd668c58b76c4e. Report an issue: GitHub.

Appendix: source

Thrown at crates/pumpkin-protocol/src/ser/mod.rs:21

use std::io::{Read, Write};

use crate::{
    FixedBitSet,
    codec::{
        bit_set::BitSet, var_int::VarInt, var_long::VarLong, var_uint::VarUInt, var_ulong::VarULong,
    },
};

use pumpkin_nbt::{
    compound::NbtCompound, deserializer::NbtReadHelper, serializer::NbtWriteHelperJava, tag::NbtTag,
};
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::{text::TextComponent, version::JavaMinecraftVersion};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum ReadingError {
    #[error("EOF, Tried to read {0} but No bytes left to consume")]
    CleanEOF(String),
    #[error("incomplete: {0}")]
    Incomplete(String),
    #[error("too large: {0}")]
    TooLarge(String),
    #[error("{0}")]
    Message(String),
}

impl serde::de::Error for ReadingError {
    fn custom<T: std::fmt::Display>(msg: T) -> Self {
        Self::Message(msg.to_string())
    }
}

#[derive(Debug, Error)]
pub enum WritingError {
    #[error("IO error: {0}")]

View on GitHub (pinned to 8d4639e25a)