diff --git a/include/grpc/impl/codegen/byte_buffer_reader.h b/include/grpc/impl/codegen/byte_buffer_reader.h
index 2ae3f1e20e4974f0e673c26b773e60bcee572372..dc0f15496fd52ee8c05758a8222fb06f31c98fdc 100644
--- a/include/grpc/impl/codegen/byte_buffer_reader.h
+++ b/include/grpc/impl/codegen/byte_buffer_reader.h
@@ -29,7 +29,7 @@ struct grpc_byte_buffer_reader {
   struct grpc_byte_buffer *buffer_in;
   struct grpc_byte_buffer *buffer_out;
   /** Different current objects correspond to different types of byte buffers */
-  union {
+  union grpc_byte_buffer_reader_current {
     /** Index into a slice buffer's array of slices */
     unsigned index;
   } current;
diff --git a/include/grpc/impl/codegen/compression_types.h b/include/grpc/impl/codegen/compression_types.h
index e39c13e88d815e3ab39fa6ae00e4a3a48caf59ed..f1b2de3f7dfd9cfd5595794a2c1244af835091b6 100644
--- a/include/grpc/impl/codegen/compression_types.h
+++ b/include/grpc/impl/codegen/compression_types.h
@@ -84,7 +84,7 @@ typedef struct grpc_compression_options {
    * behind \a GRPC_COMPRESSION_CHANNEL_DEFAULT_LEVEL. If present, takes
    * precedence over \a default_algorithm.
    * TODO(dgq): currently only available for server channels. */
-  struct {
+  struct grpc_compression_options_default_level {
     int is_set;
     grpc_compression_level level;
   } default_level;
@@ -92,7 +92,7 @@ typedef struct grpc_compression_options {
   /** The default channel compression algorithm. It'll be used in the absence of
    * call specific settings. This option corresponds to the channel argument key
    * behind \a GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM. */
-  struct {
+  struct grpc_compression_options_default_algorithm {
     int is_set;
     grpc_compression_algorithm algorithm;
   } default_algorithm;
diff --git a/include/grpc/impl/codegen/grpc_types.h b/include/grpc/impl/codegen/grpc_types.h
index 2b2036b24f035a2f1a610d9647e6a18702bf40ac..8813ec8f35fd281e3868b0b2a1c504a808b46341 100644
--- a/include/grpc/impl/codegen/grpc_types.h
+++ b/include/grpc/impl/codegen/grpc_types.h
@@ -41,11 +41,11 @@ typedef enum {
 typedef struct grpc_byte_buffer {
   void *reserved;
   grpc_byte_buffer_type type;
-  union {
-    struct {
+  union grpc_byte_buffer_data {
+    struct /* internal */ {
       void *reserved[8];
     } reserved;
-    struct {
+    struct grpc_compressed_buffer {
       grpc_compression_algorithm compression;
       grpc_slice_buffer slice_buffer;
     } raw;
@@ -104,10 +104,10 @@ typedef struct grpc_arg_pointer_vtable {
 typedef struct {
   grpc_arg_type type;
   char *key;
-  union {
+  union grpc_arg_value {
     char *string;
     int integer;
-    struct {
+    struct grpc_arg_pointer {
       void *p;
       const grpc_arg_pointer_vtable *vtable;
     } pointer;
@@ -391,7 +391,7 @@ typedef struct grpc_metadata {
   /** The following fields are reserved for grpc internal use.
       There is no need to initialize them, and they will be set to garbage
       during calls to grpc. */
-  struct {
+  struct /* internal */ {
     void *obfuscated[4];
   } internal_data;
 } grpc_metadata;
@@ -491,25 +491,25 @@ typedef struct grpc_op {
   uint32_t flags;
   /** Reserved for future usage */
   void *reserved;
-  union {
+  union grpc_op_data {
     /** Reserved for future usage */
-    struct {
+    struct /* internal */ {
       void *reserved[8];
     } reserved;
-    struct {
+    struct grpc_op_send_initial_metadata {
       size_t count;
       grpc_metadata *metadata;
       /** If \a is_set, \a compression_level will be used for the call.
        * Otherwise, \a compression_level won't be considered */
-      struct {
+      struct grpc_op_send_initial_metadata_maybe_compression_level {
         uint8_t is_set;
         grpc_compression_level level;
       } maybe_compression_level;
     } send_initial_metadata;
-    struct {
+    struct grpc_op_send_message {
       struct grpc_byte_buffer *send_message;
     } send_message;
-    struct {
+    struct grpc_op_send_status_from_server {
       size_t trailing_metadata_count;
       grpc_metadata *trailing_metadata;
       grpc_status_code status;
@@ -523,16 +523,16 @@ typedef struct grpc_op {
         object, recv_initial_metadata->array is owned by the caller).
         After the operation completes, call grpc_metadata_array_destroy on this
         value, or reuse it in a future op. */
-    struct {
+    struct grpc_op_recv_initial_metadata {
       grpc_metadata_array *recv_initial_metadata;
     } recv_initial_metadata;
     /** ownership of the byte buffer is moved to the caller; the caller must
         call grpc_byte_buffer_destroy on this value, or reuse it in a future op.
        */
-    struct {
+    struct grpc_op_recv_message {
       struct grpc_byte_buffer **recv_message;
     } recv_message;
-    struct {
+    struct grpc_op_recv_status_on_client {
       /** ownership of the array is with the caller, but ownership of the
           elements stays with the call object (ie key, value members are owned
           by the call object, trailing_metadata->array is owned by the caller).
@@ -542,7 +542,7 @@ typedef struct grpc_op {
       grpc_status_code *status;
       grpc_slice *status_details;
     } recv_status_on_client;
-    struct {
+    struct grpc_op_recv_close_on_server {
       /** out argument, set to 1 if the call failed in any way (seen as a
           cancellation on the server), or 0 if the call succeeded */
       int *cancelled;
diff --git a/include/grpc/impl/codegen/slice.h b/include/grpc/impl/codegen/slice.h
index 5ec439eb3773b78ccb56c512c8355181c4985876..a04c683a558841d58a80e297b3bbbba90ccd746d 100644
--- a/include/grpc/impl/codegen/slice.h
+++ b/include/grpc/impl/codegen/slice.h
@@ -75,12 +75,12 @@ typedef struct grpc_slice_refcount {
    of data that is copied by value. */
 struct grpc_slice {
   struct grpc_slice_refcount *refcount;
-  union {
-    struct {
+  union grpc_slice_data {
+    struct grpc_slice_refcounted {
       uint8_t *bytes;
       size_t length;
     } refcounted;
-    struct {
+    struct grpc_slice_inlined {
       uint8_t length;
       uint8_t bytes[GRPC_SLICE_INLINED_SIZE];
     } inlined;
diff --git a/tools/run_tests/sanity/core_untyped_structs.sh b/tools/run_tests/sanity/core_untyped_structs.sh
new file mode 100755
index 0000000000000000000000000000000000000000..792dd68fdd0f54041531a8447329b18de632c031
--- /dev/null
+++ b/tools/run_tests/sanity/core_untyped_structs.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# Copyright 2017 gRPC authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -e
+
+cd `dirname $0`/../../..
+
+#
+# Make sure that all core struct/unions have a name or are typedef'ed
+#
+
+egrep -Irn '(struct|union) *{' include/grpc |
+    egrep -v typedef |
+    diff - /dev/null
+
diff --git a/tools/run_tests/sanity/sanity_tests.yaml b/tools/run_tests/sanity/sanity_tests.yaml
index a86ebee7b41672664e1231514bbe2eee4530af5a..7e582bc40bf6bc31301effd47f2efe6af3a601e1 100644
--- a/tools/run_tests/sanity/sanity_tests.yaml
+++ b/tools/run_tests/sanity/sanity_tests.yaml
@@ -6,6 +6,7 @@
 - script: tools/run_tests/sanity/check_test_filtering.py
 - script: tools/run_tests/sanity/check_tracer_sanity.py
 - script: tools/run_tests/sanity/core_banned_functions.py
+- script: tools/run_tests/sanity/core_untyped_structs.sh
 - script: tools/buildgen/generate_projects.sh -j 3
   cpu_cost: 3
 - script: tools/distrib/check_copyright.py