Sharing Rust builds across git worktrees with kache 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. 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. It 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. On 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. What kache does kache 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. Each 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. Setting it up cargo install kache --locked kache init 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. I 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. I 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: kache config set cache.fallback sccache And 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. Our test builds didn't share anything at first Each 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. The 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. 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: lambda workers/handler/.cargo/config.toml target.'cfg all ' rustflags = "-C", "instrument-coverage" Coverage 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. We 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. If kache isn't sharing your builds, look for a flag like this in your Cargo config. kache explain