> ## Documentation Index
> Fetch the complete documentation index at: https://site.aspect.build/llms.txt
> Use this file to discover all available pages before exploring further.

# CODEOWNERS and Bazel

> Learn how to manage code ownership in monorepos using CODEOWNERS, OWNERS files, and tools like Bazel for better code review workflows.

export const BlogPost = ({title, date, authors, tags, image, children}) => {
  const tagList = tags ? tags.split(", ").filter(Boolean) : [];
  const tagSlug = t => t.toLowerCase().replace(/&/g, "").replace(/\+/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
  const formattedDate = date ? new Date(date + "T00:00:00").toLocaleDateString("en-US", {
    year: "numeric",
    month: "long",
    day: "numeric"
  }) : "";
  return <section className="w-full flex justify-center px-4 py-12 md:py-16">
      <div style={{
    maxWidth: "800px",
    width: "100%"
  }}>
        {image && (typeof image === "string" ? <img noZoom src={image} alt={title} className="w-full rounded-xl mb-8" style={{
    maxHeight: "400px",
    objectFit: "cover"
  }} /> : <div className="blog-post-hero-image">{image}</div>)}
        <h1 className="text-3xl md:text-4xl font-bold text-zinc-900 dark:text-white">
          {title}
        </h1>
        <div className="flex flex-wrap items-center gap-3 mt-4 text-sm text-zinc-500 dark:text-zinc-400">
          {authors && <span>{authors}</span>}
          {authors && formattedDate && <span>·</span>}
          {formattedDate && <span>{formattedDate}</span>}
        </div>
        {tagList.length > 0 && <div className="flex flex-wrap gap-2 mt-3">
            {tagList.map(tag => <a key={tag} href={"/blog/tags/" + tagSlug(tag)} className="px-2 py-0.5 rounded-full text-xs bg-zinc-100 dark:bg-zinc-800 text-zinc-600 dark:text-zinc-400 hover:bg-blue-100 dark:hover:bg-blue-900/40 hover:text-blue-700 dark:hover:text-blue-300 transition">
                {tag}
              </a>)}
          </div>}
        <hr className="my-8 border-zinc-200 dark:border-zinc-700" />
        <div className="prose dark:prose-invert max-w-none">{children}</div>
      </div>
    </section>;
};

export const MarketingPage = () => <div className="marketing-page-marker" style={{
  display: "none"
}} />;

export const Section = ({children, className = "", gray = false, dark = false, id}) => <section id={id} className={`w-full flex justify-center px-4 py-16 md:py-24 ${gray ? "bg-gray-50 dark:bg-zinc-900" : dark ? "bg-zinc-900 dark:bg-zinc-950" : ""} ${className}`}>
    <div className="w-full" style={{
  maxWidth: "1140px"
}}>
      {children}
    </div>
  </section>;

<MarketingPage />

<BlogPost title="CODEOWNERS and Bazel" date="2024-03-20" authors="Alex Eagle" tags="Bazel, Monorepo">
  <img noZoom src="https://mintcdn.com/aspectbuild/x1L7Iep716jCyJVo/images/blog/hashnode/hn-a8a7130c32.jpeg?fit=max&auto=format&n=x1L7Iep716jCyJVo&q=85&s=5e7e16dc93d6d0b3c94391d57f8d1981" alt="" className="blog-post-cover" width="1509" height="694" data-path="images/blog/hashnode/hn-a8a7130c32.jpeg" />

  Gating submission of code changes on the right reviewers is a critical and nuanced problem.

  Most companies are using GitHub for code review. It supports a single file named `CODEOWNERS` in the root of the repository. This clearly wasn't designed for monorepos, where you want each org to maintain its own "Ownership" semantics, and beneath that each team may want overrides. It ought to follow the example of many linters which treat any source file as governed by the nearest ancestor configuration file.

  Bazel is a monorepo build and test tool, but it's closely related to code review as well, so this is the sort of problem we could expect it to solve. The naive answer is to encode ownership in Bazel's dependency graph, like with [https://github.com/zegl/rules\_codeowners](https://github.com/zegl/rules_codeowners) (disclaimer, I'm a contributor there)

  However, `rules_codeowners` layering on top of the dependency graph is not ideal. It requires a parent folder to list its children, and then those are listed in a big registration block in `generate_codeowners`. You don't want to declare such a dependency graph, because it inverts the graph and causes "eager fetches" - in order to load the root package you accidentally have to load `//my_org/your_slow_team` and `//other_org/made_bad_choices` - making builds slow for everyone.

  Note that Google's monorepo has a separate file, which is just a textproto called `OWNERS`. Bazel (aka. blaze) is not involved. It's similar to [https://www.kubernetes.dev/docs/guide/owners/](https://www.kubernetes.dev/docs/guide/owners/) from what I can tell.

  What you really want is a "whole-repo operation" that reads the data files spread around the repository, and Bazel isn't a good choice for such operations since any given node in the dependency graph should have a limited transitive reachable scope based on the dependencies in the source code.

  ### Paid options

  I've used a standalone service before like [https://www.pullapprove.com/](https://www.pullapprove.com/) - this gives you a great deal of expressiveness in policies around code changes. However it just integrates with GitHub as an additional status on PRs, the same as a CI system. It doesn't understand your GitHub teams or play with the built-in "Owned by" feature in the GitHub user interface [https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#about-code-owners](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#about-code-owners)

  So let's say we really want `CODEOWNERS` but for it to work with monorepo.

  ### Okay so what could we do instead

  There's a key observation: in reviewing a commit that modifies an OWNERS file, we don't need the new values to be "live" in evaluating the policies of who reviews that change. Quite the opposite: if I make a PR to remove your team from the set of required owners of some file, your team should be required to approve that change. This means we're fine with the OWNERS semantics applying only *after* the commit gets merged to the `main` branch.

  This means we can treat CODEOWNERS as a continuous delivery problem. For any green commit on `main` we can aggregate OWNERS files from the whole repository into a correct CODEOWNERS file, then "deliver" that with a bot commit back into the repository whenever it changes.
</BlogPost>
