apache/cassandra · error · IOException

Unhandled CheckStatusReply kind:

Error message

Unhandled CheckStatusReply kind: 

What it means

CheckStatusReply.Serializer.deserialize reads a one-byte kind tag and switches on it; any byte that is not a known CheckStatusReply kind (NACK, OK, FULL, etc.) hits the default branch and throws an IOException. This indicates the incoming byte stream does not match the wire format this version of the code understands — usually corruption, a truncated/misaligned stream, or a peer running an incompatible version that serializes a kind this node does not know.

Source

Thrown at src/java/org/apache/cassandra/service/accord/serializers/CheckStatusSerializers.java:206

            KeySerializers.nullableRoutingKey.serialize(ok.homeKey, out);
            CommandSerializers.invalidIf.serialize(ok.invalidIf, out);

            if (!(reply instanceof CheckStatusOkFull))
                return;

            CheckStatusOkFull okFull = (CheckStatusOkFull) ok;
            CommandSerializers.nullablePartialTxn.serialize(okFull.partialTxn, out, version);
            DepsSerializers.nullablePartialDeps.serialize(okFull.stableDeps, out);
            CommandSerializers.nullableWrites.serialize(okFull.writes, out, version);
        }

        @Override
        public CheckStatusReply deserialize(DataInputPlus in, Version version) throws IOException
        {
            byte kind = in.readByte();
            switch (kind)
            {
                default: throw new IOException("Unhandled CheckStatusReply kind: " + Integer.toHexString(Byte.toUnsignedInt(kind)));
                case NACK:
                    return CheckStatusNack.NotOwned;
                case OK:
                case FULL:
                    KnownMap map = knownMap.deserialize(in);
                    SaveStatus maxKnowledgeStatus = CommandSerializers.saveStatus.deserialize(in);
                    SaveStatus maxStatus = CommandSerializers.saveStatus.deserialize(in);
                    Ballot maxPromised = CommandSerializers.ballot.deserialize(in);
                    Ballot maxAcceptedOrCommitted = CommandSerializers.ballot.deserialize(in);
                    Ballot acceptedOrCommitted = CommandSerializers.ballot.deserialize(in);
                    Timestamp executeAt = ExecuteAtSerializer.deserializeNullable(in);
                    boolean isCoordinating = in.readBoolean();
                    Durability durability = CommandSerializers.durability.deserialize(in);
                    Route<?> route = KeySerializers.nullableRoute.deserialize(in);
                    RoutingKey homeKey = KeySerializers.nullableRoutingKey.deserialize(in);
                    Infer.InvalidIf invalidIf = CommandSerializers.invalidIf.deserialize(in);

                    if (kind == OK)

View on GitHub (pinned to 88fd0f6a0e)

Solutions

  1. Verify all cluster nodes run a Cassandra version whose CheckStatusReply kind enum matches (complete rolling upgrade).
  2. Log the hex kind byte and compare against the current CheckStatusReply kinds to identify which value the peer sent.
  3. If a new kind was added to CheckStatusReply, add a matching case to CheckStatusSerializers so older/newer nodes can decode it.
  4. Check for earlier deserialization errors in the same connection that could have misaligned the stream; restart the failed message/session.

Example fix

// before
case NACK: return CheckStatusNack.NotOwned;
case OK:
case FULL: ...
// after
default: throw new IOException("Unhandled CheckStatusReply kind: " + Integer.toHexString(Byte.toUnsignedInt(kind)));
// add a case for any newly introduced kind, e.g.
case NEW_KIND: return CheckStatusNewReply.deserialize(in);
Defensive patterns

Strategy: try-catch

Try / catch

try {
    CheckStatusReply reply = CheckStatusReply.Serializer.deserialize(in, version);
} catch (IOException e) {
    if (e.getMessage().startsWith("Unhandled CheckStatusReply kind")) {
        logger.warn("Incompatible/unknown CheckStatusReply from peer; dropping message", e);
        // drop or request re-send; do not retry on same misaligned stream
    } else throw e;
}

Prevention

When it happens

Trigger: DataInputPlus stream containing a kind byte outside the enumerated CheckStatusReply kind values when deserializing a CheckStatusReply, e.g. during Accord inter-node message deserialization after a version mismatch or stream corruption.

Common situations: Rolling upgrades where a newer node sends a CheckStatusReply kind an older node cannot decode; network corruption or deserialization misalignment from a previously failed read; debugging/tests feeding synthetic bytes.

Related errors


AI-assisted analysis of apache/cassandra@88fd0f6a0e (2026-09-10). Data as JSON: /api/errors/190c236adec9cc02. Report an issue: GitHub.