Bazel Migration: 21 Open PRs and a Mass Extinction

2026/03/12

BUILDbazeldevexmonorepo

I have 21 open pull requests on our monorepo right now. Twenty-one. Some of them are from three weeks ago. Some of them are experiments for approaches we already abandoned. Some of them duplicate work that my tech lead already shipped while I was still polishing mine.

This is a post about how that happened, what I’m going to do about it, and why the number 21 is actually the symptom, not the disease.

How you end up with 21 open PRs

Toolchain work isn’t like feature work. When you’re figuring out how to get torch into Bazel, you don’t pick one approach and iterate — you fork the repo three times and try three approaches simultaneously. Option A: standard PyPI torch wheels. Option B: uv.override_requirement() for system torch. Option C: register Docker’s Python as a Bazel toolchain. Each one gets a branch, each branch gets a PR for CI feedback. You need CI to tell you where each approach falls apart.

Option C won. It merged as PR #21169 on March 3. The big one. System torch in Docker, py_venv_test with include_system_site_packages, tag-based CI routing. Unblocked 30 packages. I wrote a deep dive about it. That was the good part.

The bad part: Options A and B are still sitting there as open PRs. Plus the even earlier experiments — a platform-select approach from mid-February, a macOS smoke test, a “make torch optional in mai_config” attempt that only unblocked one package. Five dead PRs just from the torch experiments alone.

That’s the nature of parallel experimentation. You need to run multiple approaches to find the winner. But I never went back and closed the losers.

The Ryan problem

Ryan is my tech lead. He’s fast. He ships PRs while I’m still writing my commit message. This is not a complaint — it’s exactly what you want in a lead. The problem is coordination, and it’s entirely on my end.

Here’s what happened with three of my PRs:

PR #22575 (Mar 3) — I removed torch from the excluded packages list. Ryan’s #23296 (Mar 5) did the same thing. His merged. Mine is still open, doing nothing.

PR #23401 (Mar 6) — I migrated mai_config to Bazel. Ryan’s #23994 (Mar 9) migrated mai_config, mai_logger, and audio_segmentation in one shot. His merged. Mine is sitting there like a museum exhibit.

PR #22578 (Mar 3) — I set up a CC toolchain for building Python sdists from source. Ryan’s #21967 (same day, Feb 27 actually) took a simpler approach to the same problem. His merged. Mine is more hermetic but nobody needs that right now. He left a TODO for it, which is the right call.

Three PRs. All superseded. In each case, Ryan shipped a cleaner or broader version while I was still iterating. Not because he stole my work — we were attacking the same migration from different angles, which is fine. The problem is I didn’t notice when his stuff landed. I wasn’t checking.

The audit

Time to sort the wreckage. All 21 PRs, categorized by what to do with them.

Layer 1: Dead experiments (CLOSE — 5 PRs)

These are the torch experiments that lost the bake-off. Option C won. These are historical artifacts:

PR Date What Why it’s dead
#19185 Feb 13 Platform select for torch Pre-dates all three Options
#20793 Feb 23 Smoke-test @pypi//torch on macOS Option B precursor
#21167 Feb 24 Option A: Linux CPU torch wheels Option C won
#21168 Feb 24 Option B: resolve @pypi//torch Option C won
#20799 Feb 23 Make torch optional in mai_config Path A — only unblocked 1 package

No ambiguity here. Close all five with a comment linking to #21169 as the winner.

Layer 2: Superseded by Ryan (CLOSE — 4 PRs)

Ryan shipped it. Mine is redundant. Move on.

PR Date What Ryan’s version
#22575 Mar 3 Remove torch from excluded packages #23296 (merged Mar 9)
#23401 Mar 6 Migrate mai_config to Bazel #23994 (merged Mar 11)
#22578 Mar 3 CC toolchain for sdist builds #21967 (merged Mar 3, simpler)
#20901 Feb 23 Batch migrate 22 packages Stale — packages migrated individually

Close with a comment noting the superseding PR. No drama.

Layer 3: Maybe still shippable (EVALUATE — 3 PRs)

These need a closer look. They might have work that nobody else has done:

PR Date What Question
#23305 Mar 5 Merge py_venv_test into py_test with auto-detection Has anyone else done this DX improvement?
#22601 Mar 3 Migrate 6 packages Have these packages been migrated in other PRs?
#23478 Mar 6 Migrate mai_kernels with GPU test support Unique — GPU test infra in Bazel

#23305 is the most interesting. It’s a DX improvement that collapses two macros into one — you write py_test and it auto-detects whether the test needs system site-packages. Nobody else has done this because it’s not urgent, but it’d be nice. Worth rebasing and re-evaluating.

#23478 — mai_kernels with GPU tests in Bazel — is genuinely novel. No one else has tackled GPU test support. But it’s also the kind of thing that might need Ryan’s input on approach before I invest more time.

#22601 depends entirely on whether those 6 packages have been migrated elsewhere. Need to cross-reference against recent merged PRs.

Layer 4: Experiment sprint (DECIDE — 9 PRs)

These are all from March 9-10, a sprint where I prototyped a bunch of dep graph tools for a blog series:

PR What
#23805 Complete dep graph with BUILD stubs
#24011 BUILD file drift detector
#24016 find_changed vs Bazel rdeps comparison
#24017 Scoped pyright via dep graph
#24026 Version bump detection
#24027 Non-Python deps as filegroup targets
#24037 GPU test targeting tool
#24257 BUILD files for bus/ completer namespace
#24260 BUILD files for bus/ oai namespace

These are demo PRs. Proof-of-concepts for blog content. They were never meant to merge as-is — they’re illustrations of “here’s what a full dep graph unlocks.” The question is: do they stay open as reference material, or do I close them and keep the code in branches?

I think close. Open PRs have a cost even when you know they’re experiments. They show up in team PR counts, they nag at you in the dashboard, and anyone browsing the repo sees 9 PRs from me that look like unfinished work. Keep the branches. Close the PRs. Reference them from the blog posts if anyone wants the code.

The real problem

Here’s the part I’ve been avoiding.

I haven’t given Ryan a proper status update in over a week. Not because I’m slacking — I’ve been heads-down on these experiments, the torch migration landed, packages are being migrated. There’s plenty of work happening. The problem is I don’t have a clean picture to present.

When you have 21 open PRs and half of them overlap with your lead’s work and you’re not sure which of your experiments are still relevant, the natural instinct is to wait. “I’ll update Ryan when I have a clearer picture.” “Let me close the dead PRs first, then I’ll have something coherent to report.” “Once I evaluate the three maybe-shippable ones, I’ll know what to say.”

This is the trap. The feeling of “I don’t have a clear picture” is exactly when you should be talking to your lead. They have context you don’t. Ryan knows which packages he’s planning to migrate next. He knows whether the CC toolchain TODO is on the roadmap or a nice-to-have. He might have opinions about the dep graph experiments. The conversation that feels hardest to have is the one with the highest information density.

The “I’ll update when I have something clean” instinct is procrastination wearing a productivity costume.

The action plan

Today:

  1. Close 9 dead/superseded PRs. Layers 1 and 2. Quick comments linking to the winning PRs. Takes 20 minutes.
  2. Close 9 experiment PRs. Layer 4. Comment noting they’re blog demo code, keep the branches. Another 10 minutes.
  3. Evaluate 3 potentially shippable PRs. Cross-reference #22601 against recently merged migrations. Check if #23305 still applies cleanly. Decide on #23478 approach.
  4. Draft a status update for Ryan. Cover: what’s alive, what’s dead, what I need input on (mai_kernels GPU approach, dep graph tools value, py_test auto-detection priority). Send it even if it feels messy.

This week:

  1. Set a personal rule: no more than 5 open PRs at any time. If I open a sixth, I close or merge one first. This is like the “one in, one out” rule for closets, except for code.
  2. Check Ryan’s merged PRs every morning before starting work. Takes 2 minutes. Prevents the “oops I reimplemented what he shipped yesterday” situation.

The meta-lesson

Open PRs are cognitive debt. Every stale PR is a background process running in your head — “what was I doing there? is that still relevant? should I update it?” — and the cost is invisible until you realize you’ve got 21 of them and your mental model of the migration is a tangle of half-finished threads.

In a fast-moving migration where your tech lead is also actively shipping, you need to sync more often, not less. The parallel experimentation phase generated a lot of branches. That’s fine — that’s how toolchain work goes. But the cleanup phase is not optional, and “close the losers” needs to happen within days of picking the winner, not weeks.

Close aggressively. Sync frequently. Your open PR count is a proxy for how much unresolved ambiguity you’re carrying around.

Twenty-one is too many. Let’s get it to three.