Incremental builds: only changed fragments
Incremental builds are a build strategy where only the changed files, modules, or packages are recompiled and only the tests covering changed code re-run, with each agent given its own worktree pipeline and the agent CI itself hardened as an internet-facing surface.
- Agent CI runs are isolated from untrusted input: no event data interpolated into shell steps, and agent passes run as separate jobs with separately scoped tokens
- Parallel agents get per-worktree pipelines rather than serialising on one runner
- CI completes in under 5 minutes (median)
- P95 CI duration is under 8 minutes
- Build system supports hermetic builds (reproducible outputs regardless of machine)
- CI run duration dashboard showing median under 5 minutes
- Remote cache configuration and cache hit rate metrics
- Build configuration showing incremental/changed-only targeting
- Infrastructure L2 (Build System) - basic build caching must be in place before remote caching is meaningful
What It Is
Incremental builds are a build strategy where only the files, modules, or packages that have changed since the last build are recompiled, and only the tests that cover changed code are re-executed. Rather than rebuilding everything from scratch on every commit - the default behavior of most CI pipelines - incremental builds compute the minimal set of work required to validate a change.
The mechanism varies by build system. In Gradle, incremental compilation tracks which Java source files have changed and recompiles only those files and their transitive dependents. In TypeScript, tsc --incremental maintains a tsconfig.tsbuildinfo file that tracks which source files changed and recompiles accordingly. In Bazel, incremental builds are a core property: every build action declares its inputs, and Bazel only re-executes actions whose inputs have changed. In monorepo setups using tools like Turborepo (JavaScript), Nx (JavaScript/TypeScript), or Pants (Python/Java), the build graph is explicit and only the affected packages are built and tested.
For AI agents generating many small, focused commits, incremental builds are disproportionately valuable. An agent fixing a bug in a single module typically changes 2-3 files in a codebase of thousands. Without incremental builds, CI rebuilds and retests the entire codebase for that 3-file change. With incremental builds, CI rebuilds only the changed module and the modules that depend on it. On a large monorepo, this can reduce CI time from 8 minutes to 45 seconds for a typical agent-generated change. The impact compounds: agents that generate 20 small changes per hour get a 10x CI speedup compared to agents working against full-rebuild CI.
At L3 the unit of incrementality is increasingly the worktree rather than the branch. Meta's Muse Code, released 2026-08-05, spawns its subagents in isolated git worktrees, and a per-worktree pipeline is the natural counterpart: each concurrent agent gets its own checkout, its own build cache namespace and its own CI run, so parallel agents do not invalidate each other's incremental state.
The other thing that changed in August is that agent CI stopped being an internal system. At Black Hat USA on 2026-08-05, Novee Security showed a single public GitHub issue reaching CI secrets in three vendors' own repositories: Gemini CLI via CVE-2026-12537 (CVSS v4 10.0, OS command injection through a crafted .gemini/.env executing before sandbox init), Claude Code via CVE-2026-54316 chained with a command-validator quote-stripping bug, and Codex with no CVE but a fix that split workflow jobs and documented instruction files as an untrusted input surface. A Snowflake .NET connector issue disclosed 2026-08-17 leaked a Jira token the same way, by interpolating github.event.issue.title into a run: block. Fast incremental CI that hands an attacker your tokens is not an improvement.
The relationship between incremental builds and test impact analysis is complementary. Incremental builds reduce the compilation and build time by only recompiling changed code. Test impact analysis reduces the test execution time by only running tests affected by changed code. Mature CI pipelines at L3-L4 implement both: incremental compilation followed by targeted test selection.
Why It Matters
- CI time scales with change size, not codebase size - a 3-file change in a 500-module monorepo should take seconds to validate, not minutes; incremental builds make CI time proportional to the change, not the total codebase
- Agents benefit disproportionately - agents generate many small, focused changes; incremental builds turn each of those small changes into a fast CI run, dramatically increasing the iteration rate
- Enables monorepo CI to stay fast as codebases grow - full-rebuild CI degrades as codebases grow; incremental build CI stays fast because it only rebuilds the changed sub-graph
- Reduces compute cost proportionally - fewer compilation and test execution actions mean lower CI compute costs; for teams with many agent-generated commits per day, the cost savings are substantial
- Makes the "change small things often" pattern sustainable - incremental builds reward the small, focused commit style that AI agents naturally produce, reinforcing the pattern rather than punishing it
- Per-worktree pipelines keep parallel agents from thrashing the cache - concurrent agents sharing one checkout invalidate each other's incremental state on every write; a worktree per agent keeps each cache namespace stable
- Agent CI is internet-facing whether you treat it that way or not - three vendors shipped fixes in August after a single public issue reached CI secrets, so the same pipeline that makes agents fast is also the one holding your tokens
Getting Started
- Identify your build system's incremental build support - Gradle (Java/Kotlin): enable
--build-cacheandorg.gradle.incremental=true. TypeScript: usetsc --incrementalorts-node --transpile-onlyfor faster type checking. Rust: Cargo is incremental by default when CARGO_INCREMENTAL=1. Go: the build cache is incremental by default. JavaScript monorepos: consider Turborepo or Nx for package-level incremental builds. - Preserve the incremental build cache across CI runs - Incremental build state is stored on disk (
.gradle/,.tsbuildinfo,target/directory for Rust). Configure your CI cache to persist these directories between runs, keyed on the relevant source files. Without cache persistence, incremental builds restart from scratch every CI run and provide no benefit. - For monorepos: implement affected package detection - Turborepo's
--filter=[HEAD^1], Nx'saffected:test, and Pants'::with change detection all determine which packages are affected by a commit and run builds and tests only for those packages. Setting up these tools is a one-time investment that immediately benefits all subsequent CI runs. - Validate incremental build correctness - Incremental builds can have correctness bugs: a change that should trigger a downstream rebuild doesn't. Run the full build weekly (or on merge to main) to catch any incremental build correctness issues. Compare the incremental build results to the full build results and investigate any discrepancies.
- Configure your CI system to pass build state between jobs - If your CI pipeline splits into multiple jobs, each job needs access to the incremental build state. Pass build outputs between jobs using CI artifacts or a shared build cache. Don't split the build across jobs in a way that forces each job to recompile independently.
- Give each concurrent agent its own worktree and cache namespace -
git worktree addper agent session, with the build cache keyed by worktree, so ten parallel agents do not serialise on one checkout or repeatedly invalidate one shared incremental state. - Grep every workflow for
${{ github.event.* }}inside arun:block - this is the one-line audit that would have caught the Snowflake connector leak. Then split agent passes into separate jobs with separate checkouts, scope tokens per job rather than per workflow, and treat instruction files (AGENTS.md,.gemini/,.claude/) as attacker-controlled input. - Measure the before-and-after impact on CI time distribution - Track p50 and p95 CI time before and after enabling incremental builds. The p50 should drop significantly (typical changes benefit most). The p95 may not drop as much (changes to widely-depended-on modules still trigger large sub-graph builds). The distribution tells you where the remaining optimization opportunity is.
Turborepo's remote caching feature (available with Vercel or self-hosted) combines package-level incremental builds with a remote cache shared across developers and CI. This means when CI runs after a developer builds locally, it can hit the remote cache for packages the developer already built. The combination of incremental builds and remote caching is the JavaScript ecosystem's equivalent of Bazel.
Common Pitfalls
Incremental build false negatives. Not all build systems correctly detect all changes that should trigger a rebuild. If your build system uses file modification timestamps rather than content hashes to detect changes, fast file copies or git operations that don't change content can confuse it. Use content-hash based change detection (Bazel does this by default; Gradle supports it with --no-rebuild and correct task input declaration).
Cache invalidation bugs masking incremental build bugs. If your CI cache is misconfigured and always misses, incremental builds appear to work correctly because they always run from scratch - but you're not getting the speedup. Check whether your CI runs are actually restoring incremental build state by adding a log step that reports the size of the cache restore.
Applying incremental builds without correct dependency declarations. Build system tools infer dependencies between targets from explicit declarations (Bazel BUILD files, Turborepo turbo.json, Nx project.json). If a target has an implicit dependency that isn't declared, incremental builds won't rebuild it when its upstream dependency changes, producing incorrect results. Complete and accurate dependency declarations are required for correct incremental builds.
Over-relying on incremental builds and neglecting full builds. Incremental builds skip work by design. Over time, accumulated incremental build state can become stale or incorrect. Teams that never run full builds lose the safety net of a clean, from-scratch validation. Run full builds on merge to main and nightly on the main branch to catch anything that slips through incremental validation.
Treating the agent CI job as an internal system. The pipeline that runs an agent over a pull request usually has the widest token scope in the repository and reads text that anyone on the internet can write. The August disclosures all followed the same shape: untrusted text from an issue or an instruction file reaching a shell. Split the agent pass into its own job with its own checkout and its own minimally-scoped token, and never interpolate event data directly into a run: block.
Implementing incremental builds in one language without others. In polyglot codebases, implementing Gradle incremental builds for Java while leaving Python builds as full rebuilds creates a two-speed CI. The total CI time is dominated by the slowest full-rebuild component. Address all languages in the build or the improvement is partial.
How Different Roles See It
Bob's team runs a Java monorepo with 15 services. CI is at 9 minutes, and the breakdown shows that 6 minutes is Java compilation. The team has already enabled Gradle build cache locally but not in CI. When Bob hears this, he realizes that the incremental build state being computed by developers locally is not being shared with CI - CI is doing full rebuilds while developers' local builds are already fast.
Bob should ask the CI owner to enable Gradle remote build cache with a simple self-hosted cache node (Gradle provides a Docker image for this). The CI configuration change is minimal - add --build-cache to the Gradle command and point to the remote cache URL. The expected result: CI compilation drops from 6 minutes to under 1 minute for typical branch builds that share compilation state with recent developer or CI runs. Bob should set a two-week measure period after the change and report the results to the team as a concrete example of infrastructure investment paying off quickly.
Sarah's CI timing analysis shows that 68% of CI runs for the JavaScript monorepo are building all 12 packages even when only 1-2 packages changed. She knows Nx has an affected command that would run builds and tests only for the packages affected by the current commit, but it hasn't been connected to CI.
Sarah should quantify the waste: if 68% of runs build 12 packages when they should build 2-3 packages, the team is doing 4-6x more work than necessary on most CI runs. At 9-minute CI, correctly scoped CI would take approximately 2-3 minutes for the typical change. Sarah should present this calculation to Bob alongside the Nx affected configuration - a two-day engineering task to implement - and frame it as "we are currently paying for and waiting for 6x more work than we need on every typical commit." The data makes the priority self-evident.
Victor has implemented Nx's affected command on his team's JavaScript monorepo and the results are clear: p50 CI time dropped from 7 minutes to 2 minutes after the change, because most agent-generated commits touch 1-2 packages in a 10-package repo. He's using Turborepo remote caching with Vercel for the remote artifact store. His agents now iterate at 30 cycles per hour instead of 8.
Victor should write up the configuration as a reference implementation: the turbo.json or nx.json configuration, the CI YAML changes, and the Vercel remote cache setup. He should include the before-and-after timing data and the agent iteration rate improvement. This reference implementation is what other JavaScript teams in the organization need to replicate the result. Victor should also investigate whether the pattern extends to the organization's Python services - Pants has equivalent affected-package detection and is worth evaluating for those teams. Before he publishes the reference implementation, he should run one grep across every workflow file for ${{ github.event.* }} inside run: blocks and split the agent pass into its own job with its own token scope. A reference implementation that other teams copy is exactly the wrong place to standardise an injection path.
Further Reading
From the Field
Recent releases, projects, and discussions relevant to this maturity level.
How This Guide Changed
What each edition changed in this guide, newest first.
- V1.6September 2026LATEST
The matrix item grew two clauses this month and the guide followed. Per-worktree pipelines got their own treatment, because concurrent agents sharing one checkout spend their time invalidating each other's incremental state rather than building - Meta's Muse Code shipping with worktree-isolated subagents on 2026-08-05 made the pattern hard to ignore. The larger addition is security: after Black Hat, where a single public GitHub issue was shown reaching CI secrets in three vendors' own repositories, this guide now treats the agent CI job as an internet-facing surface and carries the
${{ github.event.* }}grep as a first-class getting-started step. - V1.0March 2026
A March original, aimed at the default that most pipelines never question: rebuilding everything on every commit. It set out what changes that, build system by build system, from Gradle's incremental compilation and TypeScript's build-info file to Bazel's declared action inputs and the affected-package graphs that monorepo tooling exposes.
Where does your team actually sit on this?
This guide describes one level of one area. Run the assessment to place your team across all 16 areas, see which gates you have passed, and get a report you can take to your stakeholders.