Trunk-Based Development in Monorepos
Selective testing and merge queues keep trunk-based development fast enough for monorepos to work.

The shared dependency graph makes long-lived branches costly, but it also gives build tools the information needed to test small changes quickly and integrate them safely. The model succeeds when selective testing, merge queues, feature flags and reproducible environments work together.
Why long-lived branches become structurally unworkable in a monorepo
A two-week branch returns to main, and its author starts debugging code they did not write. Hundreds of unrelated commits have landed while the branch was open, including renamed functions, changed APIs and dependency updates. Git can resolve textual conflicts, but that does not resolve differences in what the code now means. The branch may merge cleanly while breaking a consumer of a shared package. Integration becomes a reconstruction of changes made across the repository.
That cost follows from the monorepo’s structure. Multiple apps, services and shared libraries live in one repository, with dependency relationships that carry a shared-package change into its consumers. A stale branch in a polyrepo conflicts with its own repository’s history, whereas a stale monorepo branch must reconcile changes across the shared graph. Feature branches may work adequately while the repository is small, but that experience does not establish that long-lived branches will remain workable as it grows. A GitFlow-style workflow does not remove this structural cost by postponing integration.
What trunk-based development actually requires, and why monorepos supply most of it
Trunk-based development replaces deferred integration with small changes merged frequently into a main branch that remains deployable. Branches live for hours, and incomplete work must not make main unsafe to ship. That requires fast CI, a way to hide unfinished features and a mechanism that tests changes safely when engineers merge in parallel. Without them, short branches just bring integration failures onto main sooner.
A well-structured monorepo already supplies much of this foundation. Its atomic commit model lets a shared library and its consumers change together, without having to line up releases from separate repositories. Build tools such as Nx and Bazel use the dependency graph to test only the affected work, so changes can merge often. The repository keeps flag definitions, consumers and tests together so they can change atomically. A merge queue completes the integration path by testing the state that will actually reach main. The relationship is reciprocal, because the monorepo makes frequent integration practical while frequent integration keeps its shared graph from fragmenting across stale branches.
Affected-test detection and remote caching: the CI backbone that makes frequent commits viable
When every test runs on every commit, a large monorepo’s CI pipeline gets in the way of trunk-based development. Long waits push engineers to group changes into larger PRs, so they integrate less often just when the shared graph makes it more important to do so. Affected-test detection reverses that pressure by calculating which packages a change touches and which dependents need testing. For example, npx nx affected:test --base=main runs that set of tests in Nx. A change to one package therefore does not automatically require testing the entire repository.
When a shared utility changes, the build system can identify the packages that depend on it instead of treating the edited files as the whole scope of the change. Remote caching then shares build artifacts across the team and CI, so work with results already in the cache can be skipped. Independent packages can also build concurrently while respecting dependency order.
A cached result can be reused only if it matches the conditions of the current build. Bazel’s hermetic model isolates builds from the local machine environment, providing the conditions under which that reuse can be trusted. Fast feedback then lowers the cost of each PR and encourages smaller changes. Smaller changes are easier to integrate, so CI speed supports the branch discipline.
The merge queue: how main stays green when dozens of engineers commit in parallel
Fast branch CI still leaves a gap between a passing test and a safe merge. PR A and PR B can both pass against the same state of main. Once PR A merges, PR B’s result is stale because it describes a main that no longer exists. Merging PR B can therefore break main even though both PRs were green on their own. In a monorepo, shared dependencies make this interaction especially important because separate edits can affect the same consumers.
A merge queue closes that gap by testing each PR against the projected state of main after the changes ahead of it have landed. It serializes admission to main. Affected-test detection picks the tests that apply to the combined diff, so these repeated checks can keep up. The queue is therefore a load-bearing part of scaled trunk-based development, while selective testing provides the throughput it needs.
Stacked PRs complement the queue when a larger change has a necessary sequence. Engineers break the work into dependent branches, with each part reviewed and landed in order. The stack expresses the change’s sequence, while the queue protects its integration with other work.
Feature flags and branch by abstraction: how incomplete work ships without blocking main
Short-lived branches require branch lifetime to be separate from feature lifetime, so code can merge before its behavior is released to users. Feature flags provide that separation for additive work. Incomplete code lands behind a toggle that is off in production, while engineers enable it locally during development. Once the work is ready, the flag supports gradual rollout.
The monorepo makes cross-package flags easier to coordinate because their definitions, consumers and tests live together. A change to the flag and the code that reads it can therefore land atomically, including tests for each state. Structural refactors need a different technique when a feature toggle does not fit the change. Branch by abstraction introduces an interface, keeps old and new implementations behind it, migrates traffic incrementally and then removes the old code. Replacing a database client or core library can thus proceed through small PRs.
Code ownership and atomic cross-package changes: the collaboration model TBD enables
A shared-library change provides the clearest example of the collaboration model. In a polyrepo, it can require PRs in multiple repositories, coordinated release tags and separate consumer updates. In a monorepo, the library change and downstream edits can form one atomic PR. The affected owners review the same change, CI tests the affected packages, and deployment proceeds as a coordinated update. The unit of review matches the unit of change instead of scattering it across repository boundaries.
CODEOWNERS files map directory paths to teams so reviews go to the owners who need to see them. With shared access, ownership is applied within a common workflow where reviewers can see both the shared-code edit and its consequences. That visibility depends on frequent integration, because a library change hidden on a long-lived branch cannot inform work happening on other branches. Trunk-based development makes the atomic change visible promptly rather than deferring cross-team discovery until merge time.
Ownership also needs boundaries around what packages may depend on. Nx can enforce those boundaries at the lint level, blocking accidental coupling before it spreads through the graph. Review ownership answers who must assess a change, while dependency boundaries constrain which relationships the change may introduce.
Reproducible environments as the hidden prerequisite for keeping main green
These mechanisms rely on local development and CI running under the same conditions. When toolchain versions differ, a test can pass locally and fail in CI without a corresponding defect in the code. At the pace of frequent merges, that drift erodes trust in the green-main signal. Unpinned transitive dependencies are a particularly difficult source of inconsistency because top-level libraries can remain fixed while their sub-dependencies change.
Declarative, version-controlled environment definitions address this problem by specifying the toolchain as code. OS packages, language runtimes and tooling become part of a reproducible stack. For JavaScript/TypeScript monorepos, pnpm’s catalog feature addresses a related source of inconsistency, namely packages pinning the same dependency to different versions. Such mismatches can otherwise produce type errors or downstream runtime crashes. Hermetic builds go further by isolating execution from whatever happens to be installed on a developer’s machine. With environment parity, teams can rule out drift when tests fail, so they can trust CI results as changes merge at trunk-based development speed.
Supply chain visibility as a natural output of the monorepo-TBD model
Reproducibility also gives the build pipeline a basis for documenting what software contains and how it was produced. A monorepo running trunk-based development keeps dependency changes close to the source changes that use them. Google’s monorepo shares internal code at source level in this way, using an implicit HEAD in place of internal dependency version numbers and sharing source in place of linking previously built binaries. That removes classes of transitive dependency confusion that complicate inventory across separate repositories. The same infrastructure that supports integration can therefore supply dependency provenance for an SBOM.
The EU Cyber Resilience Act requires identifying and documenting components, including through an SBOM in a commonly used machine-readable format. Its reporting obligations apply from 11 September 2026, with main obligations from 11 December 2027. An inventory can be produced through the normal CI workflow. That does not make an SBOM sufficient on its own. An inventory provides little protection if newly disclosed vulnerabilities do not trigger scanning and remediation.
The supporting practices close that loop within the delivery system. They include continuous SBOM generation in CI, dependency scanning and nightly re-scans of already-deployed versions. Artifact signing and provenance verification through SLSA and Sigstore connect the delivered artifact to its expected origin, while policy as code governs permitted components. Generating provenance alone is not enough, because SLSA Build L1 provenance is described as “trivial to bypass or forge”. Checking signed provenance against expected values puts the evidence to work as a control during delivery.
Where teams stall when adopting TBD in a monorepo, and how to avoid it
Adoption stalls when teams impose short-lived PRs without installing the mechanisms that make them safe and economical. Without affected detection, slow CI pushes teams to batch changes, and without a merge queue, changes that are green on their own can still break main. Using manual QA in place of automated testing as the integration gate adds another wait. Environment drift then adds uncertainty, prompting engineers to hold merges for manual verification. These failures reinforce one another until the team recreates long-lived-branch behavior under a different policy.
The workable sequence starts with environment reproducibility and affected-test detection, which improve the build system independently of branching strategy. A merge queue becomes necessary when daily commit volume exposes the interaction between individually passing PRs. The first large feature under the new model provides the occasion to introduce feature flags and branch by abstraction. Flags also need regular review and removal once they are no longer needed, because accumulated toggles create dependency tangles across packages.
BetterHealthcare illustrates the payoff of assembling the pieces together. Its move from GitFlow to trunk-based development with Nx, consolidating microservices into one monorepo, cut deployment time dramatically both per app and across services. Google’s monorepo shows that trunk-based development can work across a large engineering organization. The deciding factor is not the branching policy alone, but an interlocking system in which fast tests encourage small changes, the queue protects integration, flags separate release from merge, and reproducible environments make the results trustworthy.


