Skip to content
Latchkey

sbt re-downloading dependencies (Ivy/Coursier cache not restored) in CI

Every sbt run resolves and downloads the full dependency graph because the Ivy, sbt, and Coursier caches are not persisted between CI jobs. This is not a hard failure, but it makes builds slow and increases the chance a transient download aborts the run.

What this error means

sbt spends minutes on "Updating ..." / "Resolving ..." each run, and download errors appear intermittently because nothing is cached.

sbt
[info] Updating ...
[info] Resolved  dependencies
[info] Fetching artifacts ...
(every run re-downloads ~/.ivy2 and ~/.cache/coursier from scratch)

Common causes

No cache step for the sbt directories

Without an actions/cache step, ~/.ivy2, ~/.sbt, and ~/.cache/coursier start empty every job, so sbt re-resolves everything.

A cache key that never hits

A cache key that changes every run (no stable hash of build files) means the cache is saved but never restored.

How to fix it

Cache the Ivy, sbt, and Coursier directories

  1. Add an actions/cache step for the three sbt cache paths.
  2. Key it on a hash of your build definition files so it restores on unchanged deps.
  3. Re-run to confirm resolution is served from cache.
.github/workflows/ci.yml
- uses: actions/cache@v4
  with:
    path: |
      ~/.ivy2/cache
      ~/.sbt
      ~/.cache/coursier
    key: sbt-${{ hashFiles('**/*.sbt','project/build.properties','project/**/*.scala') }}
    restore-keys: sbt-

Use setup-java built-in sbt caching

setup-java can cache sbt dependencies automatically when you set cache: sbt.

.github/workflows/ci.yml
- uses: actions/setup-java@v4
  with:
    distribution: temurin
    java-version: '17'
    cache: sbt

How to prevent it

  • Cache ~/.ivy2, ~/.sbt, and ~/.cache/coursier on a stable key.
  • Include a restore-keys fallback so partial caches still help.
  • Prefer setup-java with cache: sbt for a maintained default.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →