EngineeringBuild performance

Rust build-cache hygiene study

Measures Rust build-cache growth and compares cleanup tools, filesystem behavior, and safe integration options.

Research state: Current

Summary

Rust build caches need measured, policy-driven cleanup because filesystem behavior and tool safety differ across environments.

The measurements and design options share a July 2026 verification cutoff; implementation is tracked separately.

Research question

Rust-heavy jackin work can silently turn host-local cache directories into the largest disk consumer on the machine. The July 2026 operator disk audit found ~/.cache/jackin at roughly 410 GB, with one jackin project cache target around 402 GB. Separate Rust workspaces also had large local target/ trees: one non-jackin Rust workspace around 30 GB, this jackin checkout around 18 GB, and another Rust checkout around 13 GB.

A follow-up deep audit the same week found the problem was worse than initially scoped and was growing structurally:

  • A single shared cargo target/ dir (bind-mounted into three running jackin containers) ballooned to 460 GiB within hours of cleanup. The breakdown: 42 GiB of debug/deps and 29 GiB of debug/incremental containing over 3,000 per-session incremental directories — each concurrent cargo invocation creates its own session tree, and nothing garbage-collects them.
  • An external agent (OpenAI Codex CLI) created separate CARGO_TARGET_DIR overrides (codex-otel, codex-otel-fast, codex-otel-msrv, msrv) inside the shared target, adding 111 GiB of near-identical compilation artifacts — the same crates compiled multiple times into separate directories.
  • The host's own target/ dir held 23 GiB of artifacts from stuck cargo test processes that deadlocked on PTY I/O for hours, blocking cleanup.
  • Disk usage cycled between 240 GiB and 515 GiB on a 926 GiB volume as caches were manually cleaned and then immediately regenerated by concurrent builds.

The root bug class is not "one bad directory". jackin has multiple places that create or preserve build/cache state for speed, but there is no single Rust build-cache ownership model with quotas, TTLs, eviction order, operator-visible accounting, or an automatic cleanup path. A cache can be created because it improves launch or build latency, then survive indefinitely because no policy owns its lifetime. Worse, concurrent agent builds into shared or overlapping target directories multiply artifacts without deduplication, and incremental-compilation session directories accumulate without garbage collection.

Why it matters

Agents run many builds across many branches, isolated worktrees, generated checkouts, and PR verification bundles. Rust makes the pressure worse because Cargo stores build output under target/ by default, and those targets are per-workspace unless explicitly redirected. Compiler-result caches such as sccache solve a different layer: they avoid recompiling repeated rustc inputs, but they do not delete every workspace's target/ directory and they add their own disk cache that needs a size cap.

If jackin keeps optimizing for faster warm starts without matching cleanup mechanics, the product becomes hostile to long-running local use. The operator sees free disk disappear, macOS becomes unstable near full-disk conditions, and the natural workaround is destructive manual deletion instead of a trusted jackin prune or jackin doctor flow.

The fix should be structural: every cache jackin creates or recommends must have an owner, a purpose label, a default budget, a cleanup mechanism, and a visibility surface.

Tool comparison

Why a comparison is needed

The credible options differ materially in cache storage and eviction. kache (github.com/kunobi-ninja/kache) directly addresses the disk-pressure failure mode measured in the July 2026 audit, while sccache has broader ecosystem maturity. The comparison below keeps the decision evidence-based.

Feature matrix

FeaturesccachekacheccacheCARGO_INCREMENTAL=0 only
What it cachesCompiler results (rustc + C/C++)Compiler results (rustc + C/C++)C/C++ onlyNothing — just disables incremental
Already in jackin mise.tomlYes (0.16.0)NoNon/a
Already in jackin CIYes (all workflows)NoNoYes (CARGO_INCREMENTAL=0 alongside sccache)
Disk dedup across target dirsNo — each target dir stores full copiesYes — reflink/hardlink from content-addressed storeNo (C/C++ only)No
APFS reflinks (zero-copy restore)No — copies artifacts into target/Yes — copy-on-write clone, zero disk cost per restoreNon/a
Content-addressed storeNo (opaque cache)Yes — blake3 hash, identical blobs stored oncePartial (hash-based)n/a
Auto-disable incrementalNo — requires manual CARGO_INCREMENTAL=0Yes — artifact caching replaces incrementaln/an/a (it IS the disable)
Built-in target/ cleanupNoYeskache clean (TUI), kache gc (LRU eviction)NoNo
Cache key portabilityPath-sensitive (needs SCCACHE_BASEDIRS)Path-normalized by default — cross-machine keys out of boxPath-sensitiven/a
C/C++ cachingYes (separate sccache gcc wrapper)Yes (kache cc / kache c++, same store)Yes (C/C++ only)No
Remote backendsRedis, S3, GCS, Azure, GHA, Memcached, WebDAV, OSS, COSS3 (AWS, R2, Ceph, MinIO)Remote (limited)n/a
Multi-level cacheYes (disk,redis,s3 chains with backfill)No (local + optional S3 sync)Non/a
Distributed compilationYes (icecream-style)NoNon/a
Maturity / adoptionVery high — Mozilla-backed, 7.5k stars, shipped since 2016New — v0.10.0, 380 stars, 2026Very high (C/C++ world)Cargo built-in
GitHub Actions integrationsccache-actionkache-actionn/an/a
Install methodsbrew, cargo, Nix, scoop, wingetmise, brew, cargo-binstall, APT, winget, AURbrew, apt, all distrosCargo built-in
Daemon modelClient-server (auto-start, 10 min idle timeout)Optional daemon (local cache works without it)Nonen/a
Monitor / dashboardsccache --show-stats (text)kache monitor (live TUI), kache report (Perfetto/Chrome trace)ccache --statisticsn/a

sccache — strengths and limits

sccache (Mozilla, Apache-2.0) is a mature ccache-like compiler wrapper for Rust, C/C++, CUDA, and HIP. It is already installed via jackin's mise.toml (cargo:sccache = "0.16.0") and wired as RUSTC_WRAPPER with CARGO_INCREMENTAL=0 across all CI workflows.

Strengths for jackin:

  • Already integrated — zero new dependencies to adopt.
  • Most remote backends of any tool (Redis, S3, GCS, Azure, GHA, Memcached, WebDAV, OSS, COS).
  • Multi-level cache chains (disk,redis,s3) with automatic backfill — a small local L0 plus a remote L1 can eliminate cold builds across CI runners and developer machines.
  • Distributed compilation (icecream-style with authentication and sandboxing).
  • SCCACHE_BASEDIRS for path normalization across checkouts.

Limits for jackin's disk problem:

  • No disk deduplication. Each target/ directory stores full copies of compiled artifacts. Five separate target dirs (e.g. codex-otel, codex-otel-fast, debug, msrv) each contain their own copies of the same .rlib/.rmeta files. sccache makes rebuilds fast after cargo clean, but the target dirs themselves still balloon.
  • Does not manage target dirs. No built-in mechanism to find, report, or clean stale target/ trees. The operator must know to look for them and delete manually.
  • Requires explicit CARGO_INCREMENTAL=0. Without it, incremental compilation sessions accumulate in target/debug/incremental/ — the July 2026 audit found 3,091 session directories totaling 64 GiB in a single shared target. sccache documents this as a known caveat: "Incrementally compiled crates cannot be cached."
  • Opaque cache. The cache store is not content-addressed in a way the operator can inspect. sccache --show-stats gives hit/miss counts but not "which crates are cached" or "how much disk is duplicated across targets."

kache — strengths and limits

kache (Kunobi, Apache-2.0) is a zero-copy, content-addressed build cache for Rust and C/C++. It is a drop-in RUSTC_WRAPPER and a cc/c++ compiler wrapper. Cache keys are blake3 hashes of normalized compiler inputs; cache hits restore zero-copy — a reflink (copy-on-write clone) where the filesystem supports it (APFS, btrfs, XFS-with-reflink), and a hardlink or copy otherwise.

Strengths for jackin:

  • APFS reflinks eliminate the multi-target-dir disk explosion. On macOS (APFS), kache restores cached artifacts into target/ via copy-on-write reflinks. Multiple target dirs sharing the same compiled crate share the same physical disk blocks. Five target dirs × 40 GiB each could be ~40 GiB of unique blocks instead of 200 GiB. This directly solves the codex-otel* directory multiplication problem.
  • Content-addressed store deduplicates identical blobs. Every compiled artifact is stored by blake3 hash. If two different crate compilations produce identical output (common for crates in multiple feature sets), they share one blob file. The SQLite index tracks references; GC removes blobs only when no entry references them.
  • Automatically disables incremental compilation. kache disables incremental on every platform while wrapping rustc, because artifact caching replaces that path and avoids APFS-related incremental-compilation corruption in git worktrees. No need for a separate CARGO_INCREMENTAL=0 env var.
  • Built-in target/ management. kache clean is a TUI that finds target/ directories, shows what percentage of each is cached, and can safely delete them. kache gc provides LRU or age-based eviction. kache stats / kache report give detailed hit/dup/miss breakdowns including Perfetto/Chrome traces.
  • Path-normalized cache keys by default. Cross-machine portability without SCCACHE_BASEDIRS configuration. A developer's laptop and a CI runner with the same toolchain produce identical keys.
  • C/C++ caching in the same store. kache cc / kache c++ wraps gcc/clang/clang-cl object compiles. The jackin workspace's heavy C dependencies (aws-lc-sys, rocksdb, secp256k1) would be cached alongside Rust artifacts in one store.
  • Daemon is optional for local caching. Local hits and misses work without the daemon. The daemon only matters for async S3 uploads, remote checks, and prefetching.
  • Available via mise. mise use -g github:kunobi-ninja/kache@latestjackin already uses mise for all tool management.

Limits for jackin:

  • Newer project. v0.10.0, 380 GitHub stars, first released in 2026. Less battle-tested than sccache (7.5k stars, shipping since 2016). Risk: undiscovered bugs, API instability, smaller community.
  • Fewer remote backends. Only S3-compatible (AWS, R2, Ceph, MinIO). No Redis, GCS, Azure, GHA, or Memcached backends yet. Multi-level cache chains not available.
  • No distributed compilation. sccache's icecream-style distributed compilation is not available.
  • Container filesystem caveat. Inside OrbStack/Docker containers on macOS, bind-mounted paths from the host go through VirtioFS. Reflinks work on APFS (native macOS) but may fall back to hardlinks or copies through VirtioFS. The content-addressed store and automatic incremental disabling still apply, but the reflink disk deduplication benefit is reduced for container-internal target dirs. Host-native builds get full reflink benefits.
  • Not yet in jackin CI. Adopting kache for CI would require replacing sccache-action with kache-action across workflows.

ccache — not applicable

ccache is the original C/C++ compiler cache. It does not support Rust. sccache was created as a Rust-aware successor. For jackin's mixed Rust + C/C++ workload, sccache or kache both subsume ccache's C/C++ coverage while adding Rust.

CARGO_INCREMENTAL=0 alone — necessary but insufficient

Setting CARGO_INCREMENTAL=0 eliminates the incremental session directory explosion (64 GiB in the July audit). It is already set in jackin CI alongside sccache. But it does not:

  • Deduplicate artifacts across target dirs.
  • Provide cache hits (rebuilds are full, not incremental).
  • Manage or clean target dirs.
  • Cache C/C++ compilation.

It is a prerequisite for any sccache/kache setup (both require or benefit from incremental disabled), not a standalone solution.

Recommendation

Primary: kache for host + containers; sccache retained for CI until kache-action is proven.

The disk deduplication problem is structural — multiple concurrent agent builds with separate CARGO_TARGET_DIR values creating near-identical artifact copies. Only kache's reflink/content-addressed approach solves this at the storage layer. sccache, no matter how well configured, still results in full-copy target dirs.

For CI, sccache is retained because it is already wired, has GHA cache integration, and CI runners are ephemeral (disk dedup is less critical than cache-hit speed).

If kache proves unstable or insufficient in practice, sccache with SCCACHE_DIR scoped under the jackin cache root and SCCACHE_CACHE_SIZE capped remains the fallback. The env-var surface (RUSTC_WRAPPER, CARGO_INCREMENTAL) is the same for both tools, so switching is a one-variable change.

Filesystem behavior matrix

The disk savings from any build cache depend heavily on the filesystem. This matrix shows what happens in each environment jackin operates in:

Filesystemreflink (CoW)hardlinkkache restore strategyDedup benefit
APFS (macOS host)YesYesreflink — zero-copy clone, independent inodeMaximum — target dirs share blocks via CoW
VirtioFS (OrbStack bind mount)Unlikely¹Yes²hardlink or copyPartial — content-addressed store deduplicates; target dirs may still copy
ext4 (Linux container root)No³Yeshardlink (immutable artifacts), copy (bin/dylib)Partial — store deduplicates; targets use hardlinks where possible
btrfs (Linux)YesYesreflinkMaximum
tmpfsNoYeshardlink or copyPartial
NTFS (Windows)NoNo⁴copyNone — full copies

¹ VirtioFS passes through to the host APFS filesystem but does not expose clonefile(2) / FICLONE ioctl to the guest. kache detects reflink failure and falls back to hardlink or copy. The content-addressed store still deduplicates; only the target-dir copy is affected.

² Hardlinks work on VirtioFS for immutable artifacts (.rlib/.rmeta) because they share the same underlying inode on the host APFS filesystem. Mutable artifacts (bin/dylib/proc-macro) are copied to prevent post-link modification from corrupting the store.

³ ext4 does not support reflink/COW. reflink=on requires btrfs or XFS with reflink=1 mount option.

⁴ Windows does not expose a portable inode link count. kache reports zero hardlink savings on Windows.

Key insight: Even when reflinks are unavailable (containers), kache's content-addressed store means the cache itself is deduplicated — identical blobs stored once regardless of how many builds reference them. The target-dir duplication is the remaining cost, and it is bounded by kache gc eviction plus kache clean for stale dirs.

Design

Research notes

  • Cargo stores build output in target and build directories, defaulting to a workspace-local target directory; CARGO_TARGET_DIR, Cargo config build.target-dir, or --target-dir can redirect it. See the Cargo Book build-cache reference.
  • Cargo workspaces already share one output directory within a workspace, but Cargo does not currently provide a stable built-in user-wide target cache for every workspace. Cargo tracks the requested shared cross-workspace cache, but it remains future work rather than a shipped feature jackin can depend on.
  • A single global CARGO_TARGET_DIR can reduce duplicate target trees, but it has sharp edges for jackin: concurrent builds can serialize on Cargo locks, cargo clean can remove artifacts for unrelated workspaces, and path-dependency identity/collision issues have been reported when multiple workspaces share one target directory. See Cargo issue rust-lang/cargo#12516.
  • sccache (mozilla/sccache, 7.5k stars, Apache-2.0) is a ccache-like compiler wrapper for Rust and C/C++. It stores compiler results on local disk or remote backends (Redis, S3, GCS, Azure, GHA, Memcached, WebDAV, OSS, COS). Local disk storage has explicit controls: SCCACHE_DIR changes the cache location, SCCACHE_CACHE_SIZE caps size (default 10 GB). Multi-level cache chains (disk,redis,s3) with automatic backfill are supported. Path normalization across checkouts is via SCCACHE_BASEDIRS. sccache documents that incrementally compiled crates cannot be cached — CARGO_INCREMENTAL=0 is required. See the sccache README and Configuration reference.
  • kache (kunobi-ninja/kache, v0.10.0, Apache-2.0) is a zero-copy, content-addressed build cache for Rust and C/C++. Cache keys are blake3 hashes of normalized compiler inputs (rustc version, crate name, source content, dependencies, flags, target triple, compile-time env). Local hits restore zero-copy: reflink (copy-on-write) on APFS/btrfs/XFS-with-reflink, hardlink for immutable artifacts (.rlib/.rmeta) on other filesystems, copy for mutable outputs (bin/dylib/proc-macro). The store is content-addressed — identical blobs stored once, shared across all cache entries. Incremental compilation is disabled automatically while kache wraps rustc. kache init configures RUSTC_WRAPPER + daemon; kache clean finds target/ dirs with cache breakdown; kache gc provides LRU/age-based eviction; kache doctor diagnoses setup; kache monitor is a live TUI dashboard. S3 sync (AWS, R2, Ceph, MinIO) for remote cache. Available via mise (mise use -g github:kunobi-ninja/kache@latest), Homebrew, cargo-binstall, APT, winget, AUR. See the kache docs and deduplication reference.
  • Incremental compilation (-C incremental) is Cargo's per-session optimization for fast rebuilds after small source changes. It creates per-session directories under target/debug/incremental/ that are never garbage-collected. Concurrent cargo invocations each create their own session directories, multiplying the cache. Disabling it (CARGO_INCREMENTAL=0) eliminates this class of disk growth at the cost of slower iterative development — but sccache/kache artifact caching compensates by making full rebuilds fast. See the Cargo profiles reference.
  • APFS clonefile (clonefile(2), FICLONE ioctl) creates a copy-on-write clone of a file — zero disk cost until one side modifies the data. kache uses this on APFS to restore cached artifacts into target/ without duplicating blocks. Multiple target dirs sharing the same compiled crate consume disk only once. See kache's deduplication reference.
  • The Rust project has explicitly discussed cache cleaning as an ongoing Cargo concern, including global cache cleanup and future target-directory tracking work. See the Rust Blog cache-cleaning post.

Proposed direction

Introduce a Rust build-cache hygiene program for jackin with five layers:

  1. Inventory and ownership. Define every jackin-managed cache class: project build targets under ~/.cache/jackin/projects, global Cargo registry/git caches, compiler-result cache (kache or sccache), generated role/agent prefetch caches, docs/build output, PR verification bundles, and per-workspace target/ paths when jackin intentionally sets them. Each class gets an owner, purpose, default retention policy, and deletion safety rule.
  2. Out-of-box compiler cache. Install and wire a compiler-result cache automatically in every jackin container — no operator setup required. The construct image ships with the cache tool installed; the runtime env sets RUSTC_WRAPPER and CARGO_INCREMENTAL=0; a shared cache store is bind-mounted into all containers for a project (like the existing Cargo registry mount). See Out-of-box integration below.
  3. Bounded defaults. Do not create unbounded Rust build targets under ~/.cache/jackin. For Rust-heavy jackin-managed builds, prefer a bounded policy: compiler cache enabled by default, cache size capped (KACHE_CACHE_DIR or SCCACHE_CACHE_SIZE), cache store scoped under the jackin cache root, and CARGO_TARGET_DIR scoped per project or per verification bundle only when a cleanup owner exists.
  4. Prune and doctor integration. Extend jackin prune and jackin doctor so operators can see and clean Rust build/cache classes separately from role images, instance state, and registry caches. The first useful operator surface is likely jackin prune cache --dry-run showing bytes by class, followed by explicit --rust-builds, --compiler-cache, or equivalent class selectors. If kache is the active cache, kache clean and kache gc can be delegated to for rich target-dir management.
  5. Automatic guardrails. Add a soft budget checker that warns before a jackin-owned cache root crosses a configured threshold, and an optional TTL/least-recently-used cleanup path for safe classes. Automatic deletion must never remove operator source trees or non-jackin caches silently; it may only touch paths jackin owns and labels.

Out-of-box integration

The goal: when an operator spawns a jackin container for a Rust project, the container automatically has a working compiler cache with incremental compilation disabled — no manual mise install, no env-var editing, no kache init/sccache --start-server. The cache is shared across all containers for the same project, and it is visible in jackin doctor / jackin prune.

Construct image changes

docker/construct/Dockerfile

Add the compiler cache tool to the construct image. Two options:

Option A — kache (recommended):

# Install kache for zero-copy build caching (reflink on APFS, hardlink elsewhere).
# kache disables incremental compilation automatically when wrapping rustc.
RUN curl -fsSL https://r2.kunobi.com/kache/apt/gpg.key | gpg --dearmor -o /etc/apt/keyrings/kache.gpg && \
    echo "deb [signed-by=/etc/apt/keyrings/kache.gpg] https://r2.kunobi.com/kache/apt stable main" \
      > /etc/apt/sources.lists.d/kache.list && \
    apt-get update && apt-get install -y --no-install-recommends kache && \
    rm -rf /var/lib/apt/lists/*

This follows the construct image's AGENTS.md rule: prefer the official package-manager source (APT for kache on Debian). If the APT package is unavailable for the target arch, fall back to mise use -g github:kunobi-ninja/kache@latest (mise is already installed in the image).

Option B — sccache (fallback, already in mise.toml):

sccache is already pinned in mise.toml as cargo:sccache = "0.16.0". No Dockerfile change needed — mise installs it. But the env vars must be set (see below).

Container environment

Set in the Dockerfile or the runtime env injection (crates/jackin-capsule/src/session.rs / crates/jackin-runtime/src/runtime/docker_profile.rs):

# Disable incremental compilation — prevents unbounded session-dir growth.
# Both sccache and kache require/benefit from this.
CARGO_INCREMENTAL=0

# Wire the compiler cache wrapper. Use whichever tool is installed.
RUSTC_WRAPPER=kache   # or: RUSTC_WRAPPER=sccache

# Scope the cache store under the jackin-owned cache root.
# This dir is bind-mounted from the host (see below).
KACHE_CACHE_DIR=/home/agent/.cache/kache     # kache
# SCCACHE_DIR=/home/agent/.cache/sccache     # sccache alternative
# SCCACHE_CACHE_SIZE=20G                      # sccache size cap

Shared cache mount

Add a bind mount in the container spawn logic, parallel to the existing Cargo registry mount:

~/.cache/jackin/global/kache  →  /home/agent/.cache/kache

Or for sccache:

~/.cache/jackin/global/sccache  →  /home/agent/.cache/sccache

This makes all containers for all projects share one compiler cache. A crate compiled in one container is a cache hit in another. The mount follows the same pattern as the existing:

~/.cache/jackin/global/cargo/registry  →  /home/agent/.cargo/registry
~/.cache/jackin/global/cargo/git       →  /home/agent/.cargo/git

mise.toml [env] section

Add an [env] section to mise.toml so local development builds (outside containers) also use the cache:

[env]
CARGO_INCREMENTAL = "0"
RUSTC_WRAPPER = "kache"
# kache disables incremental automatically; CARGO_INCREMENTAL=0 is belt-and-suspenders
# for environments where kache is not yet installed.

CI workflow alignment

jackin CI already uses sccache (RUSTC_WRAPPER: sccache, CARGO_INCREMENTAL: "0") across all workflows. If kache is adopted for local/container use, CI can either:

  1. Keep sccache — CI runners are ephemeral, disk dedup is less critical than cache-hit speed. sccache's GHA cache integration is proven. This is the pragmatic default.
  2. Switch to kache — use kache-action instead of sccache-action. Evaluate after local/container adoption proves kache's stability.

Both tools use RUSTC_WRAPPER, so the CI env-var surface is identical. Switching is a one-line workflow change per job.

Operator visibility

Extend jackin doctor to report:

Rust build cache:
  Tool: kache 0.10.0
  Cache store: ~/.cache/jackin/global/kache (12.4 GiB / 20 GiB budget)
  Target dirs: 3 active (~45 GiB total, ~18 GiB after reflink dedup)
  Incremental: disabled (CARGO_INCREMENTAL=0)

Extend jackin prune cache to delegate to the cache tool:

# kache:
jackin prune cache --rust-builds   # runs `kache clean` (interactive TUI)
jackin prune cache --compiler      # runs `kache gc --max-age 7d`

# sccache:
jackin prune cache --compiler      # stops server, clears SCCACHE_DIR, restarts

Host setup (operator's macOS machine)

For operators who also build Rust on the host (not just inside containers), install the cache tool globally:

kache (recommended for APFS reflink benefits):

mise use -g github:kunobi-ninja/kache@latest
kache init    # configures RUSTC_WRAPPER in ~/.cargo/config.toml + starts daemon
kache doctor  # verify setup

sccache:

brew install sccache
# Add to ~/.cargo/config.toml:
# [build]
# rustc-wrapper = "sccache"
# Set in shell profile:
# export CARGO_INCREMENTAL=0
# export SCCACHE_CACHE_SIZE=20G

On the host (native APFS), kache's reflink deduplication gives maximum benefit: every target/ dir across every Rust project shares blocks with the content-addressed store, so 10 project target dirs don't consume 10× the disk.

Specific local-case recommendation

For the disk layout that triggered this item, the target architecture is a three-cache model:

  1. Shared downloads (already shipped): Cargo registry and git checkouts in a shared cache mounted into all containers (~/.cache/jackin/global/cargo/).
  2. Shared compiler results (new): kache or sccache store in a shared cache mounted into all containers (~/.cache/jackin/global/kache/). This addresses the duplicate "same crate compiled in many containers" problem. With kache, the content-addressed store means identical blobs are stored once regardless of how many target dirs reference them.
  3. Bounded build-output targets (existing, with new budget): Cargo target output scoped by project/workspace, under ~/.cache/jackin/projects/. These remain disposable, visible in doctor, and removable by prune. With kache on APFS, the target dirs are reflink-backed — their logical size may be large but their physical disk cost is bounded by the unique-artifact count, not the directory count.
# Jackin❯-managed env vars for Rust build cache:
CARGO_INCREMENTAL=0                          # eliminate incremental session explosion
RUSTC_WRAPPER=kache                          # zero-copy compiler cache
KACHE_CACHE_DIR="$JACKIN_CACHE/global/kache"  # shared store, bind-mounted

# Project-specific (scoped, disposable):
CARGO_TARGET_DIR="$JACKIN_CACHE/projects/<project-key>/target"

Rejected defaults

  • One global CARGO_TARGET_DIR for all operator Rust workspaces. It is attractive because it removes many target/ folders, but it mixes unrelated projects, makes cargo clean blast-radius confusing, and can serialize or collide under parallel builds. It may still be useful as an advanced opt-in for a single operator who accepts those trade-offs.
  • sccache as the sole solution. sccache makes rebuilds fast after cargo clean but does not deduplicate disk across multiple target dirs. The July 2026 audit showed that the problem is not just "rebuilds are slow" — it is "the same artifacts exist in 5+ target dirs and consume 200+ GiB." Only kache's reflink/content-addressed approach addresses this at the storage layer.
  • Leaving the choice to the operator. If the compiler cache is not configured out-of-box, most operators will never set it up, and the disk pressure problem recurs. jackin should ship with a working default.

Policy questions

  • Should jackin ship kache as the default compiler cache, or sccache (already in CI), or both with a preference order?
  • Should the default Rust build target cache be per project, per branch, per PR verification bundle, or shared by a stable workspace/project key?
  • Which cache classes are safe for automatic TTL eviction, and which require explicit operator confirmation because deletion causes expensive rebuilds?
  • Should cache budget live globally, per workspace, or both?
  • Should remote/shared cache backends (kache S3, sccache Redis/S3) be role-managed configuration, operator global configuration, or out of scope for V1?
  • Should jackin expose an advanced single-CARGO_TARGET_DIR mode for operators who prefer disk deduplication over parallel-build isolation?
  • Should the construct image install kache via APT (official package) or via mise (already installed, consistent with other tools)? The construct image AGENTS.md prefers official package-manager sources when available.
  • How should jackin handle the case where an external agent (e.g. OpenAI Codex CLI) sets its own CARGO_TARGET_DIR inside the shared target? Should jackin intercept and redirect, or let it create separate dirs (relying on kache reflinks to deduplicate)?

Tasks

  1. Audit all jackin code paths that create host-side cache or build-output directories, including project cache roots, PR verification bundles, prewarm/build flows, generated role/agent cache mounts, and docs/build outputs.
  2. Add a contributor reference table for cache classes: path owner, creation path, safe deletion condition, prune command, default budget, and visibility surface.
  3. Decide the Rust build-cache architecture: kache vs sccache (or both), where the cache store lives, what default cache size/budget should be, and whether CARGO_TARGET_DIR should be redirected for jackin-managed Rust builds.
  4. Add the compiler cache tool to the construct image (docker/construct/Dockerfile) via APT or mise.
  5. Add CARGO_INCREMENTAL=0, RUSTC_WRAPPER=kache (or sccache), and KACHE_CACHE_DIR (or SCCACHE_DIR) to the container runtime environment (crates/jackin-capsule/src/session.rs, crates/jackin-runtime/src/runtime/docker_profile.rs).
  6. Add a shared compiler-cache bind mount (~/.cache/jackin/global/kache or ~/.cache/jackin/global/sccache) to the container spawn logic, parallel to the existing Cargo registry mount.
  7. Add an [env] section to mise.toml with CARGO_INCREMENTAL = "0" and RUSTC_WRAPPER for local development builds.
  8. Extend jackin prune to report and clean Rust build artifacts and compiler caches. Delegate to kache clean / kache gc when kache is the active tool.
  9. Add jackin doctor output that flags jackin-owned cache roots above budget, reports cache tool status, and names the exact cleanup command.
  10. Add tests around cache classification so new cache roots cannot be introduced without owner/policy metadata.
  11. Document operator-facing setup flows (host install) and cleanup flows once behavior ships; keep internal path and policy details in contributor reference docs.
  12. Evaluate kache stability over a 2-4 week trial period in local/container builds before deciding whether to also switch CI from sccache to kache.

On this page