hyperledger/fabric · error

unknown operation type

Error message

unknown operation type

What it means

HandleWriteBatch iterates over records in a WriteBatchState and switches on each record's Type (PUT_STATE, PUT_STATE_METADATA, etc.). A record whose type is not one of the known operation types hits the default branch and produces this error. It means the batch contains an unrecognized/unsupported write operation.

Source

Thrown at core/chaincode/handler.go:1331

			if err = h.purgePrivateData(delState, txContext, msg.ChannelId); err != nil {
				return nil, err
			}

		case pb.WriteRecord_PUT_STATE_METADATA:
			putStateMetadata := &pb.PutStateMetadata{
				Key:        kv.GetKey(),
				Collection: kv.GetCollection(),
				Metadata: &pb.StateMetadata{
					Metakey: kv.GetMetadata().GetMetakey(),
					Value:   kv.GetMetadata().GetValue(),
				},
			}
			if err = h.putStateMetadata(putStateMetadata, txContext, msg.ChannelId); err != nil {
				return nil, err
			}

		default:
			return nil, errors.New("unknown operation type")
		}
	}

	return &pb.ChaincodeMessage{Type: pb.ChaincodeMessage_RESPONSE, Txid: msg.Txid, ChannelId: msg.ChannelId}, nil
}

// Handles requests that modify ledger state
func (h *Handler) HandleInvokeChaincode(msg *pb.ChaincodeMessage, txContext *TransactionContext) (*pb.ChaincodeMessage, error) {
	chaincodeLogger.Debugf("[%s] C-call-C", shorttxid(msg.Txid))

	chaincodeSpec := &pb.ChaincodeSpec{}
	err := proto.Unmarshal(msg.Payload, chaincodeSpec)
	if err != nil {
		return nil, errors.Wrap(err, "unmarshal failed")
	}

	// Get the chaincodeID to invoke. The chaincodeID to be called may
	// contain composite info like "chaincode-name:version/channel-name".

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Align peer and chaincode shim versions so all WriteRecord types are supported
  2. Make sure every WriteRecord added to the batch explicitly sets its Type field before sending
  3. Update fabric to a version where the new operation type is handled in HandleWriteBatch

Example fix

// before
rec := &pb.WriteRecord{Key: key, Value: value} // Type unset

// after
rec := &pb.WriteRecord{Type: pb.WriteRecord_PUT_STATE, Key: key, Value: value}
Defensive patterns

Strategy: validation

Validate before calling

func validWriteType(t pb.WriteRecord_Type) bool {
	switch t {
	case pb.WriteRecord_PUT_STATE, pb.WriteRecord_PUT_STATE_METADATA:
		return true
	}
	return false
}
// validate each record before adding to the batch

Type guard

func isKnownWriteRecord(kv *pb.WriteRecord) bool {
	return kv.GetType() == pb.WriteRecord_PUT_STATE ||
		kv.GetType() == pb.WriteRecord_PUT_STATE_METADATA
}

Try / catch

resp, err := handler.HandleWriteBatch(msg, txContext)
if err != nil {
	if strings.Contains(err.Error(), "unknown operation type") {
		// drop or quarantine the offending record; version-skew likely
		return handleVersionSkew(msg)
	}
	return err
}

Prevention

When it happens

Trigger: A write batch containing a pb.WriteRecord with an unset or future/unknown Type value — e.g. a newer peer or shim emitting an operation type this handler version does not know, or a zero-valued WriteRecord that was never given a type.

Common situations: Version skew between peer and chaincode shim where a new batch operation type was added; custom shims constructing WriteRecord without setting Type; corrupted batch assembly that omits the type field.

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/a638c8bfd26f7b8a. Report an issue: GitHub.