Skip to content

fix: include plugin classpath in action cache keys - #9632

Open
stasimus wants to merge 1 commit into
sbt:developfrom
stasimus:fix/action-cache-plugin-classpath
Open

stasimus wants to merge 1 commit into
sbt:developfrom
stasimus:fix/action-cache-plugin-classpath

Conversation

@stasimus

@stasimus stasimus commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Fixes #9631. Cached task keys ignored the metabuild classpath, so plugin version changes replayed stale values and poisoned CachedCompileFailure entries until cleanFull. Adds a metabuild classpath digest to every key and keys cached failures on the full build definition. Includes scripted regression test.

@scala-cla-bot

scala-cla-bot Bot commented Aug 19, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@stasimus
stasimus force-pushed the fix/action-cache-plugin-classpath branch 2 times, most recently from 23f095e to 94ac424 Compare August 19, 2026 15:10
@stasimus

Copy link
Copy Markdown
Contributor Author

What was broken

The action cache key for a cached task is sha256(codeContentHash, hash(inputs), extraHash, cacheVersion), where codeContentHash is the hash of the task body's macro tree (Cont.scala) and extraHash is always 0. Nothing in the key reflects the metabuild classpath. A plugin like sbt-scalac-opts-plugin defines scalacOptions ++= scalacOptsFor(scalaVersion.value, scalacOptsAll) — the tree is identical across plugin versions, only the data behind it changes. So after a version bump:

  1. scalacOptions (and compile) replayed stale cached values;
  2. after cleanFull, a recompile with the stale options failed and the CachedCompileFailure was recorded under a key that ignores the plugin classpath;
  3. every subsequent compile replayed that failure for a configuration that compiles fine; only cleanFull recovered.

How it's fixed

  • On each project load, two digests are computed from already-available content hashes and passed via BuildWideCacheConfiguration:
    • metaBuildDigest: every build unit's pluginData.classpath (plugin/dependency jars + compiled project/*.scala products). Mixed into every cached task key, so any plugin or build-helper change invalidates dependent cached values.
    • buildDefinitionDigest: the above plus the Eval-generated .sbt classes. Used only to key cached failures: a failure is stored under sha256(inputDigest, buildDefinitionDigest) and can only replay while the whole build definition is byte-identical. Failure entries written by older sbt under the loose key are treated as a miss and overwritten.
  • The .sbt dsl classes are deliberately excluded from the global key so a plain build.sbt edit doesn't invalidate the entire cache (guarded by the existing cache/version-bump-compile scripted test).

Verified with the repro from https://github.com/stasimus/sbt2-stale-cache-repro: all README steps produce the expected exit codes with this patch. New scripted test cache/plugin-scalac-options-toggle covers the toggle scenario; utilCache/test, scripted cache/*, and MiMa all pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Plugin classpath not in action cache key: stale scalacOptions and replayed CachedCompileFailure

1 participant