diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 13702c0cebd2cfb06ffc829b444e842237d7c652..e4e7c47c5a3c90a1e9cb8a47045dffa4d1bbd137 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -2,17 +2,20 @@
 # Uses OWNERS files in different modules throughout the
 # repository as the source of truth for module ownership.
 /** @a11r @nicolasnoble @ctiller
+/*.podspec @muxi @makdharma @a11r @nicolasnoble @ctiller
 /binding.gyp @murgatroid99 @a11r @nicolasnoble @ctiller
 /Gemfile @murgatroid99 @a11r @nicolasnoble @ctiller
 /grpc.gemspec @murgatroid99 @a11r @nicolasnoble @ctiller
 /package.json @murgatroid99 @a11r @nicolasnoble @ctiller
 /Rakefile @murgatroid99 @a11r @nicolasnoble @ctiller
+/grpc.bzl @muxi @makdharma @a11r @nicolasnoble @ctiller
 /bazel/** @nicolasnoble @dgquintas @ctiller
 /cmake/** @jtattermusch @a11r @nicolasnoble @ctiller
 /doc/PROTOCOL-HTTP2.md @ejona86 @a11r @nicolasnoble @ctiller
 /doc/interop-test-descriptions.md @ejona86 @a11r @nicolasnoble @ctiller
 /etc/** @jboeuf @nicolasnoble @a11r @ctiller
 /examples/node/** @murgatroid99 @a11r @nicolasnoble @ctiller
+/examples/objective-c/** @muxi @makdharma @a11r @nicolasnoble @ctiller
 /examples/python/** @nathanielmanistaatgoogle @kpayson64 @mehrdada
 /include/** @ctiller @markdroth @dgquintas @a11r @nicolasnoble
 /src/core/** @ctiller @markdroth @dgquintas
@@ -34,7 +37,5 @@
 /tools/** @matt-kwong @jtattermusch @nicolasnoble @a11r @ctiller
 /tools/codegen/core/** @ctiller @dgquintas @markdroth
 /tools/distrib/python/** @nathanielmanistaatgoogle @kpayson64 @mehrdada
-/tools/dockerfile/** @matt-kwong @jtattermusch @nicolasnoble @a11r @ctiller
-/tools/run_tests/** @matt-kwong @jtattermusch @nicolasnoble @a11r @ctiller
 /tools/run_tests/artifacts/*_node* @murgatroid99 @matt-kwong @jtattermusch @nicolasnoble @a11r @ctiller
 /tools/run_tests/helper_scripts/*_node* @murgatroid99 @matt-kwong @jtattermusch @nicolasnoble @a11r @ctiller
diff --git a/OWNERS b/OWNERS
index 87958a45669457dc303b1ae4dc0c8bc77b422c4b..7e6a84c57a81acc29caa2c21de9de0c2f8a168bb 100644
--- a/OWNERS
+++ b/OWNERS
@@ -5,3 +5,5 @@
 @ctiller
 
 @murgatroid99 binding.gyp Gemfile grpc.gemspec package.json Rakefile
+@muxi *.podspec grpc.bzl
+@makdharma *.podspec grpc.bzl
diff --git a/examples/objective-c/OWNERS b/examples/objective-c/OWNERS
new file mode 100644
index 0000000000000000000000000000000000000000..c14fe53c062014406efa75d72f43fd550d77abe1
--- /dev/null
+++ b/examples/objective-c/OWNERS
@@ -0,0 +1,2 @@
+@muxi
+@makdharma
diff --git a/tools/mkowners/mkowners.py b/tools/mkowners/mkowners.py
index 4805df45dfcd5d0fa015d526830be1591225815c..2ccedfcfb8c13429cdf8081216dce3fae97877d0 100755
--- a/tools/mkowners/mkowners.py
+++ b/tools/mkowners/mkowners.py
@@ -132,7 +132,7 @@ def git_glob(glob):
   global gg_cache
   if glob in gg_cache: return gg_cache[glob]
   r = set(subprocess
-      .check_output(['git', 'ls-files', glob])
+      .check_output(['git', 'ls-files', os.path.join(git_root, glob)])
       .decode('utf-8')
       .strip()
       .splitlines())
@@ -150,7 +150,7 @@ def expand_directives(root, directives):
         globs[glob].append(directive.who)
   # expand owners for intersecting globs
   sorted_globs = sorted(globs.keys(),
-                        key=lambda g: len(git_glob(os.path.join(root, g))),
+                        key=lambda g: len(git_glob(full_dir(root, g))),
                         reverse=True)
   out_globs = collections.OrderedDict()
   for glob_add in sorted_globs:
@@ -162,8 +162,9 @@ def expand_directives(root, directives):
       files_have = git_glob(full_dir(root, glob_have))
       intersect = files_have.intersection(files_add)
       if intersect:
-        for f in files_add:
+        for f in sorted(files_add): # sorted to ensure merge stability
           if f not in intersect:
+            print("X", root, glob_add, glob_have)
             out_globs[os.path.relpath(f, start=root)] = who_add
         for who in who_have:
           if who not in out_globs[glob_add]:
@@ -182,8 +183,9 @@ def add_parent_to_globs(parent, globs, globs_dir):
           intersect = files_parent.intersection(files_child)
           gglob_who_orig = gglob_who.copy()
           if intersect:
-            for f in files_child:
+            for f in sorted(files_child): # sorted to ensure merge stability
               if f not in intersect:
+                print("Y", full_dir(owners.dir, oglob), full_dir(globs_dir, gglob))
                 who = gglob_who_orig.copy()
                 globs[os.path.relpath(f, start=globs_dir)] = who
             for who in oglob_who:
@@ -199,6 +201,7 @@ with open(args.out, 'w') as out:
   out.write('# Auto-generated by the tools/mkowners/mkowners.py tool\n')
   out.write('# Uses OWNERS files in different modules throughout the\n')
   out.write('# repository as the source of truth for module ownership.\n')
+  written_globs = []
   while todo:
     head, *todo = todo
     if head.parent and not head.parent in done:
@@ -207,6 +210,21 @@ with open(args.out, 'w') as out:
     globs = expand_directives(head.dir, head.directives)
     add_parent_to_globs(head.parent, globs, head.dir)
     for glob, owners in globs.items():
-      out.write('/%s %s\n' % (
-          full_dir(head.dir, glob), ' '.join(owners)))
+      skip = False
+      for glob1, owners1, dir1 in reversed(written_globs):
+        files = git_glob(full_dir(head.dir, glob))
+        files1 = git_glob(full_dir(dir1, glob1))
+        intersect = files.intersection(files1)
+        if files == intersect:
+          if sorted(owners) == sorted(owners1):
+            skip = True # nothing new in this rule
+            break
+        elif intersect:
+          # continuing would cause a semantic change since some files are
+          # affected differently by this rule and CODEOWNERS is order dependent
+          break
+      if not skip:
+        out.write('/%s %s\n' % (
+            full_dir(head.dir, glob), ' '.join(owners)))
+        written_globs.append((glob, owners, head.dir))
     done.add(head.dir)