feat(config): E2E_CACHE_DIR and --cache-dir override cache.dir

Merged
OK
13d ago
+288-87

Closes #1117

  • E2E_CACHE_DIR and --cache-dir <dir> replace cache.dir, so worktrees and checkouts of one project can share a replay cache without editing the config.
  • Precedence: --cache-dir > E2E_CACHE_DIR > cache.dir > .e2e/cache. The mode is untouched and --no-cache still wins.
  • Both resolve from the project root, like cache.dir and --output, and go through the same output-overlap checks. Error messages name the source (would hold --cache-dir .e2e/results/c under results/).
  • Read once in resolveConfig (packages/e2e/src/config/resolve.ts, cacheDirOverride), so workers, watch mode, and e2e cache all get the same directory. A blank E2E_CACHE_DIR counts as unset, the same rule as other E2E_* variables.
  • The flag is on run and cache ls|stats|clear. explore and mcp don't use the cache, so they only get the variable through the output-overlap check.
  • --cache-dir together with a custom cache.store is INVALID_CONFIG, because the flag would name a directory nothing reads. The ambient variable is ignored there.
  • REPLAY_STALE advice no longer says "commit" for a cache directory outside the project.
  • Vitest blanks E2E_CACHE_DIR and scripts/bench-ab.ts drops it, so a developer's shared cache never leaks into fixture projects or A/B runs.
  • Docs: cache.mdx gets a "Share the cache across checkouts" section (with the note that checkouts need the same projectId or package name), plus reference/cli, reference/config, reference/environment, and skills/e2e/references/running.md.
E2E_CACHE_DIR=~/.cache/my-app/e2e npx e2e run
npx e2e run --cache-dir ../shared-cache

Verified

Ran it locally: yes

  • The built CLI on a scratch project in /tmp, copied into two separate checkouts (checkout-a, checkout-b). It has a web target, one agent.act step, and a scripted model that logs each call, so a replay is visible as zero model calls.

checkout-a, E2E_CACHE_DIR=/tmp/verify-1117/shared e2e run records:

 ✓ target "web" command ready 89ms
[scripted model] call 1
[scripted model] call 2
   ✓ increments the counter > agent.act "increment the counter once" 399ms · 2 model calls
 Test Files  1 passed (1)
         AI  4 tokens · 2 model calls · scripted/scripted-counter
      Cache  1 missed

checkout-b, same variable, replays from the shared directory with no model call. Its own .e2e/ has no cache/:

 ✓ target "web" command ready 94ms
   ✓ increments the counter > agent.act "increment the counter once" 172ms
 Test Files  1 passed (1)
      Cache  1 replayed

Other checks in checkout-b:

$ E2E_CACHE_DIR=../shared e2e cache ls
TEST                                              TARGET  INSTRUCTION   AGE  ACTIONS
tests/counter.e2e.ts::increments%20the%20counter  web     bc8b5c526994  <1m  1
$ e2e run --cache-dir ../shared                                    -> Cache  1 replayed
$ E2E_CACHE_DIR=/tmp/nowhere e2e run --cache-dir /tmp/.../shared   -> Cache  1 replayed (flag wins)
$ e2e run                                                          -> Cache  1 missed (default .e2e/cache)
$ E2E_CACHE_DIR=/tmp/.../shared e2e run --no-cache                 -> 2 model calls, no Cache line
$ E2E_CACHE_DIR=.e2e e2e run
output ".e2e" (the default) is the cache directory .e2e or inside it; keep results and the replay cache apart
$ e2e run --cache-dir .e2e/results/c
output ".e2e" (the default) would hold --cache-dir .e2e/results/c under results/, which the run owns; move --cache-dir or the output