Which caches must be cleared to recover a ClojureScript build after a failed toolchain upgrade?
0 reputation · 19 Feb 2023, 08:59 UTC
0 reputation · 19 Feb 2023, 08:59 UTC
I'm planning a ClojureScript and shadow-cljs version bump on a project and want a documented recovery path in case the upgrade fails or is interrupted mid-build, leaving partially compiled output behind.
From what I understand, several layers of cached artifacts exist: the compiler's :output-dir contents, shadow-cljs's .shadow-cljs/cache, the .cpcache from tools.deps, and the local Maven repository. It's unclear to me which of these actually need to be removed to guarantee a clean, consistent rebuild versus which can safely be kept to avoid a lengthy dependency re-download.
Assume a recent ClojureScript (1.11.x) with shadow-cljs 2.x and deps.edn; I'd verify recovery by rebuilding and comparing the bundle against a fresh-clone build.
Which caches are strictly required to clear after a failed upgrade, and which are safe to preserve? Does the answer change between shadow-cljs and plain cljsbuild workflows? Is there any risk that stale source maps or foreign-lib metadata survive a partial clean and silently corrupt the next build?
32775 reputation · 19 Feb 2023, 11:37 UTC
For a shadow-cljs project, two directories are strictly required to clear:
.shadow-cljs/cache:output-dir in your build config, e.g. public/js or target/public/js)For a plain cljsbuild/Leiningen project, you must delete:
target/cljsbuild or resources/public/js).cljs_node_repl or .cljs_browser_replThe tools.deps classpath cache (.cpcache) and the local Maven repository (~/.m2/repository) are safe to preserve. They only affect dependency resolution, not the compiler's incremental state, so keeping them avoids a lengthy re-download.
Shadow-cljs stores incremental analysis, foreign-lib metadata, and build state inside .shadow-cljs/cache. If an upgrade is interrupted mid-build, that directory can hold a mix of artifacts written by the old and new compiler versions. The cache format is not guaranteed to be compatible across ClojureScript versions, so a partial clean that leaves it behind risks the next build reusing stale analysis and producing silently incorrect output. This is the main corruption vector, and it is exactly the failure mode your fresh-clone comparison is designed to catch.
The :output-dir holds compiled JavaScript and source maps. Shadow-cljs does not auto-purge it on version change, so it must be removed before restarting the build. Stale source maps left there do not corrupt subsequent compilation, but they can mislead debuggers; clearing the directory removes the ambiguity entirely.
In a cljsbuild workflow the same logic applies, but there is no single clean command: the compiler's incremental state lives in the output directory and ad-hoc REPL caches, so script their removal explicitly.
# shadow-cljs
shadow-cljs clean # removes .shadow-cljs/cache and :output-dir
# or manually:
rm -rf .shadow-cljs/cache
rm -rf public/js # your :output-dir
shadow-cljs compile app
# plain cljsbuild
rm -rf target/cljsbuild
rm -rf .cljs_node_repl .cljs_browser_repl
lein cljsbuild once # or: clojure -M:cljsbuild once
Confirm .shadow-cljs/cache is actually gone after cleaning; its presence indicates an incomplete cleanup. Then clone the repo fresh into a temp directory, run the same compile command, and compare bundle hashes (e.g. sha256sum public/js/main.js). Finally, open the app in browser devtools and confirm source maps resolve to the correct original sources. For CI, run shadow-cljs clean before the compile step whenever the toolchain version changes.
One caveat: if you ever suspect a corrupted JAR (checksum errors, broken downloads), then clearing ~/.m2/repository becomes warranted — but that is a dependency problem, not an upgrade-recovery requirement.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.