{"slug": "sharing-rust-builds-across-git-worktrees-with-kache", "title": "Sharing Rust builds across git worktrees with kache", "summary": "Switching from a shared Cargo build directory to the kache compiler cache cut a new git worktree's Rust builds to 14 seconds and 2 GB of added disk, down from 8.2 GB per worktree, DocSpring's blog reported. kache, a compiler cache like sccache, keys each rustc compile by input content but restores cache hits as APFS copy-on-write clones, so each worktree keeps its own target/ folder and fingerprints and builds no longer contend on a single lock. The setup required one Cargo config change plus removing an exported RUSTC_WRAPPER=sccache in favor of kache config set cache.fallback sccache, and the author noted the test build initially saw no improvement while the document worker build came entirely from cache in 8 to 10 seconds.", "body_md": "In my last post, I described how we [share one Cargo build directory between git worktrees](https://docspring.com/blog/posts/rust-git-worktrees-shared-cargo-build/). I run several coding agents at once, each in its own worktree, and every worktree that built our Rust Lambda workers compiled the same 400 or so dependencies into its own `target/` folder. Pointing them all at one build directory brought each extra worktree down from 8.2 GB to 2.7 GB.\n\nIt worked, but it took some hacks. Cargo gives our own crates the same file names in every worktree, so I had to give each worktree's crates a different hash with an unstable Cargo feature, turned on with a variable from Cargo's own test suite. Every worktree also waited on the build directory's one lock.\n\nOn r/rust, [LankyLingonberry2532 asked](https://www.reddit.com/r/rust/comments/1xurvlo/comment/19sjsybwj7txc/?ref=docspring.com) why we didn't use [kache](https://github.com/kunobi-ninja/kache?ref=docspring.com). I hadn't heard of it, so I tried it. It took one change to our Cargo config to get it working, and now a new worktree's builds take 14 seconds and add 2 GB, on stable Cargo with nothing shared but the cache.\n\n## What kache does\n\nkache is a compiler cache, like sccache: Cargo runs `rustc` through it, and it keys each compile by the content of its inputs (the source files, the compiler, the flags, the dependencies' outputs). The difference is what it does on a cache hit. sccache copies the cached files into the worktree's `target/`, so every worktree still holds its own copy of every dependency. kache stores each output once and restores it as an APFS clone: a copy-on-write file that shares its disk blocks with the cache until one of them is changed.\n\nEach worktree keeps its own `target/` folder and its own fingerprints, which is how Cargo decides what's up to date. That rules out the bug from my last post, where one worktree reused another's build of our crate and ran the other branch's code. Builds don't share a lock either: two worktrees can build at the same time, and when both need the same crate, kache compiles it once.\n\n## Setting it up\n\n```\ncargo install kache --locked\nkache init\n```\n\n`kache init` shows what it will change before it changes anything. It sets kache as Cargo's `rustc-wrapper` in `~/.cargo/config.toml`, starts a background daemon (with a launchd service on macOS), and adds a few lines to your shell startup files. Those put a directory of shims in front of your `PATH`: `cargo` and `rustdoc`, which run the real tools through kache, and `cc`, `gcc`, `clang`, `c++` and `g++`, which cache C and C++ compiles. It also sets `kache test-runner` as Cargo's test runner, so test binaries wait for the CPU slots that builds use.\n\nI deleted the C and C++ shims. They would send every C compile on my machine through kache, including Ruby and Node's native extensions, and I only wanted it for Rust. I kept the `cargo` and `rustdoc` shims, because kache uses them to fill a new `target/` folder before Cargo looks at it.\n\nI had to change two more things. My shell exported `RUSTC_WRAPPER=sccache`, which overrides Cargo's config, so I removed that and set sccache as kache's fallback for anything kache doesn't cache:\n\n```\nkache config set cache.fallback sccache\n```\n\nAnd since our coding agents run the builds, I added a note to their global instructions: `cargo` is a kache shim, `du` overstates a `target/` folder's size (more on that below), and `KACHE_DISABLED=1` runs a build without kache when something looks wrong.\n\n## Our test builds didn't share anything at first\n\nEach of our worktrees makes two builds: the test build, and a plain build of the document worker that our Rails app runs in development. I tested kache with fresh copies of our repository, the same way I measured the shared build directory.\n\nThe document worker build worked straight away. In a second copy, every crate came out of the cache, and the build took 8 to 10 seconds. The test build didn't improve at all: each copy compiled almost everything again, and took about as long as a build with an empty cache.\n\n`kache explain serde` showed what was happening: the cache held six entries for the same `serde`, with the same version, features and source, one for each copy. Their keys differed in the checkout's path. Our test build's Cargo config added coverage instrumentation to every crate:\n\n```\n# lambda_workers/handler/.cargo/config.toml\n[target.'cfg(all())']\nrustflags = [\"-C\", \"instrument-coverage\"]\n```\n\nCoverage instrumentation writes source file paths into the compiled output, so kache can't reuse an instrumented crate in a checkout at another path, and it puts the path into the key. That's the right thing for kache to do, and it meant no instrumented crate could be shared.\n\nWe didn't need that config at all. Our coverage reports come from [cargo-llvm-cov](https://github.com/taiki-e/cargo-llvm-cov?ref=docspring.com), which adds its own instrumentation flags. The config only instrumented every plain `cargo test` and `cargo build`, and our image builds, CI jobs and document worker script all had to turn it off again. So I deleted it, along with the workarounds, and checked that `cargo llvm-cov` still produces its report.\n\nIf kache isn't sharing your builds, look for a flag like this in your Cargo config. `kache explain <crate>` shows which part of the key changed.\n\n## The numbers\n\nI measured these on an M2 Pro MacBook with our Lambda workers, Cargo 1.99.1 and kache 2.1.1. Each worktree makes both builds. The first two rows are from my last post.\n\n| Each worktree after the first | Disk | Time | \n|---|---|---|\n| Separate build folders | 8.2 GB | 99 seconds | \n| Shared build directory | 2.7 GB | 22 seconds | \n| kache | 2.0 GB | 14 seconds | \n\nWith kache, a second worktree's test build ran `rustc` 16 to 52 times and compiled one crate, our own. kache had already filled its `target/` folder with the dependencies from the cache, so Cargo found them up to date. The document worker build compiled nothing.\n\nThe first worktree pays for the cache. With an empty cache, its test build took 236 seconds and its document worker build 37 seconds, since kache filled the document worker's `target/` folder from the test build's dependencies.\n\nTen worktrees would take about 31 GB with kache: about 13 GB for the first, whose builds the cache shares its blocks with, and 2 GB for each one after it. That's close to the shared build directory's 33 GB, against 82 GB with separate build folders. Most of the gain is in time.\n\nI also checked that an edit in one worktree only reaches that worktree. I changed the document worker's `main` in one copy and rebuilt all three: only that copy's binary printed the change.\n\n## What to watch out for\n\n`du` and Finder show each `target/` folder at its full size, 12 GB for one of ours, because they count the cloned blocks in every folder. Measure what a worktree really costs with `df` before and after its build.\n\nThe cache has a size budget: by default 5% of the disk, at least 5 GB and at most 100 GB, and kache removes old entries when it's over. `kache clean --stale 14d` also removes `target/` folders that no build has used for two weeks, or whose worktree is gone.\n\nkache is young. The repository is from February 2026, version 1.0 came out in October, and 2.0 at the start of November. A cache like this has to put every input of a compile into its key, and a missed input would give you a stale build. That's the same kind of failure as the bug in my last post, and I haven't seen it, but if a build ever does something you can't explain, run it again with `KACHE_DISABLED=1`. `kache doctor` checks the setup, and `kache explain` shows why a crate missed the cache.\n\nI've only tried kache on macOS, and our CI doesn't use it. On Linux, it uses copy-on-write clones on filesystems that support them, such as Btrfs and XFS, and otherwise hardlinks or copies.\n\n## What we removed\n\nMoving to kache let us delete the script that wrote each worktree's Cargo config, the script that deleted the shared directory when it grew past 50 GB, the unstable feature and Cargo's test-suite variable, and the rust-analyzer setting that passed the variable to my editor. Removing the coverage config also removed a workaround from five image builds, two CI jobs and our document worker script.\n\nThe Cargo team is still working on a build cache shared between workspaces, and it's one of the [Rust project's goals for 2026](https://goals.rust-lang.org/2026/goals.html?ref=docspring.com). Until then, we're using kache.", "url": "https://wpnews.pro/news/sharing-rust-builds-across-git-worktrees-with-kache", "canonical_source": "https://docspring.com/blog/posts/rust-git-worktrees-kache/", "published_at": "2026-11-13 22:44:00+00:00", "updated_at": "2026-11-13 22:54:29.937315+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents"], "entities": ["kache", "DocSpring", "Cargo", "Rust", "sccache", "rustc", "LankyLingonberry2532", "APFS"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/sharing-rust-builds-across-git-worktrees-with-kache", "markdown": "https://wpnews.pro/news/sharing-rust-builds-across-git-worktrees-with-kache.md", "text": "https://wpnews.pro/news/sharing-rust-builds-across-git-worktrees-with-kache.txt", "jsonld": "https://wpnews.pro/news/sharing-rust-builds-across-git-worktrees-with-kache.jsonld"}}