dotnet/aspnetcore · error · RuntimeException

The length header was incomplete

Error message

The length header was incomplete

What it means

Utils.readLengthHeader reads the VarInt length prefix from the framing buffer. If the buffer has no more bytes before the VarInt terminates (continuation bit clear or the 5-byte cap reached), the prefix is incomplete and the message is considered malformed.

Solutions

  1. Accumulate more bytes before parsing - the framing layer should wait until at least one full VarInt (up to 5 bytes) is available.
  2. Treat as a fatal transport error: close and reconnect the SignalR connection.
  3. Verify the transport (WebSockets/LongPolling) is intact and not being truncated by an intermediary.

Example fix

// before - parse a buffer that may be a partial frame
List<HubMessage> msgs = protocol.parseMessages(partialBuffer, binder);
// after - wait for a complete length prefix before parsing
while (buffer.remaining() >= 1 && needsMoreForVarInt(buffer)) { awaitMoreBytes(); }
List<HubMessage> msgs = protocol.parseMessages(buffer, binder);
Defensive patterns

Strategy: validation

Validate before calling

// Ensure at least one full VarInt (up to 5 bytes) of framing is available
boolean hasFullVarInt(ByteBuffer b) {
    int mark = b.position();
    int n = 0;
    try {
        byte c;
        do { if (!b.hasRemaining()) return false; c = b.get(); n++; }
        while (n < 5 && (c & (byte)0x80) != 0);
        return true;
    } finally { ((java.nio.Buffer)b).position(mark); }
}

Type guard

static boolean isFrameComplete(ByteBuffer b) {
    return b != null && b.remaining() >= 1 && hasFullVarInt(b);
}

Try / catch

try {
    return Utils.readLengthHeader(buffer);
} catch (RuntimeException ex) {
    if ("The length header was incomplete".equals(ex.getMessage())) {
        // need to buffer more bytes from the transport; do not retry with the same buffer
        throw new java.io.IOException("Partial frame - await more bytes", ex);
    }
    throw ex;
}

Prevention

When it happens

Trigger: A truncated/partial frame arrives: the connection delivered fewer bytes than a complete VarInt length header requires (Utils.java:32-38). This is framing-level corruption, not a value-level error.

Common situations: Network truncation or a partial read, an over-aggressive buffer slice, a proxy/Load Balancer cutting the stream, or a peer sending non-SignalR bytes that happen to have the continuation bit set then stop.

Related errors


AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11). Data as JSON: /api/errors/6715761ea4f81605. Report an issue: GitHub.

Appendix: source

Thrown at src/SignalR/clients/java/signalr/messagepack/src/main/java/com/microsoft/signalr/messagepack/Utils.java:37

        // VarInt: 0x35 - %00110101 - the most significant bit is 0 so the value is %x0110101 i.e. 0x35 (53)
        // VarInt: 0x80 0x25 - %10000000 %00101001 - the most significant bit of the first byte is 1 so the
        // remaining bits (%x0000000) are the lowest bits of the value. The most significant bit of the second
        // byte is 0 meaning this is last byte of the VarInt. The actual value bits (%x0101001) need to be
        // prepended to the bits we already read so the values is %01010010000000 i.e. 0x1480 (5248)
        // We support payloads up to 2GB so the biggest number we support is 7fffffff which when encoded as
        // VarInt is 0xFF 0xFF 0xFF 0xFF 0x07 - hence the maximum length prefix is 5 bytes.
        
        int length = 0;
        int numBytes = 0;
        int maxLength = 5;
        byte curr;
        
        do {
            // If we run out of bytes before we finish reading the length header, the message is malformed
            if (buffer.hasRemaining()) {
                curr = buffer.get();
            } else {
                throw new RuntimeException("The length header was incomplete");
            }
            length = length | (curr & (byte) 0x7f) << (numBytes * 7);
            numBytes++;
        } while (numBytes < maxLength && (curr & (byte) 0x80) != 0);
        
        // Max header length is 5, and the maximum value of the 5th byte is 0x07
        if ((curr & (byte) 0x80) != 0 || (numBytes == maxLength && curr > (byte) 0x07)) {
            throw new RuntimeException("Messages over 2GB in size are not supported");
        }
        
        return length;
    }
    
    public static ArrayList<Byte> getLengthHeader(int length) {
        // This code writes length prefix of the message as a VarInt. Read the comment in
        // the readLengthHeader for details.
        
        ArrayList<Byte> header = new ArrayList<Byte>();

View on GitHub (pinned to 3600ca084e)