In this post I'll give updates about open source I worked on during July and August 2025.
To see previous OSS updates, go here.
I'd like to thank all the sponsors and contributors that make this work possible. Without you the below projects would not be as mature or wouldn't exist or be maintained at all! So a sincere thank you to everyone who contributes to the sustainability of these projects.
Current top tier sponsors:
Open the details section for more info about sponsoring.
If you want to ensure that the projects I work on are sustainably maintained, you can sponsor this work in the following ways. Thank you!
Although summer hit Europe and I made a train trip to Switzerland for some hiking with my wife, OSS activity continued in the borkiverse. 20 projects saw updates. As usual, babashka, SCI and clj-kondo saw the most activity.
One of the big things I’m looking forward to is speaking at Clojure Conj 2025. At the risk of sounding a bit pretentious, the title of my talk is "Making Tools Developers Actually Use". Babashka started as a quirky interpreter "nobody had asked for" but now many Clojure developers don't want to live without it. Clj-kondo started out as a minimal proof-of-concept linter and now is widely used tool in Clojurian's every day toolset and available even in Cursive today. In the talk I want to reflect on what makes a tool something developers (like myself) actually want to use. I'm excited about this opportunity and about my first time visiting the Conj (don't ask me how I got the Clojure Conj cap on the photo above). Given the rest of the schedule, it's something I wouldn't want to miss.
For babashka, my main focus has been making it feel even more like regular Clojure. One example is the change in how non-daemon threads are handled. Previously, people had to sometimes add @(promise)
to keep an httpkit server alive. Now babashka behaves like clojure -X
in this regard: if you spawn non-daemon threads, the process waits for them. It’s looks like a small change, but it brings consistency with JVM Clojure, something I'm always aiming for more with babashka. If you want the old behavior, you can still use --force-exit
. While implementing this I hit an interesting bug with GraalVM and also found out that clojure -X
sometimes stalls when using agents. Maybe more on this next time.
Another change that was introduced is that when code is evaluated through load-string
or Compiler/load
(which is the same thing in bb), vars like *warn-on-reflection*
are bound. This fixes a problem with loading code in non-main threads. E.g. @(future (load-string "(set! *warn-on-reflection* true)"))
would fail in previous versions of babashka. You might wonder why you would ever want to do this. Well, a similar thing happens when you execute babashka tasks in parallel and that's where I ran into this problem.
SCI, the interpreter under the hood of babashka and several other projects, got some critical fixes as well. I detected one somewhat embarrasing bug when loading clojure+.hashp
in babashka. It had code that looked like:
(def config {})
(let [config {}
_ (alter-var-root #'config (constantly config))
]
...)
In the expression (alter-var-root #'config (constantly config))
the var #'config
was mistaken for the local config
since SCI's analyzer used a resolve
-like function that also resolves locals. This fails horribly. In 6 years of SCI it's the first time I encountered this bug though. After fixing this problem, I noticed that babashka's CI acted up. On every commit, babashka CI tests dozens of Clojure libraries by running their test suites. I noticed that specter's tests were failing. It turned out that one test actually worked prior to fixing the above bug exactly because the SCI analyzer's resolve
returned a node that evaluated to a local value. But there is no way I could just leave that bug in, so I had to make a pull request to specter as well to set this straight. A new specter version was released that works both with older version of babashka and the new version.
One other headscratcher in SCI was on the ClojureScript side of things and had to do with munging. In interop like (.-foo-bar #js {:foo-bar 1})
ClojureScript munges the field name in the interop form to foo_bar
but in the object it stays "foo-bar"
. The munging of this name wasn't applied in SCI as an oversight. So in SCI (and thus in nbb, joyride, scittle, etc.) the above expression would return 1
whereas in ClojureScript it would return nil
. In contrast, (.-foo-bar #js {:foo_bar 1})
would return nil
in SCI but 1
in CLJS. Although fixing this could mean a breaking change in SCI-based scripting environments I decided to align it with CLJS anyway, as switching between SCI and CLJS should not introduce these kinds of surprises.
Other improvements in SCI were made in the area of better using type hints on instance method interop.
And then there’s clj-kondo, the linter that is supposed to spark joy ✨, as far as a linter is able to do that in a developer's life. Two new linters were added, including one that catches suspicious uses of locking. This linter was inspired by a similar rule in splint. Lots of smaller improvements were made like sorting findings and imported files such that they are consistent across multiple runs that use the --parallel
option and across operating systems. And as usual bugfixes and preventing false positives.
One happy improvement to scittle is that referencing a library that was introduced by a <script>
tag now was made a lot easier. You can find the docs about that here. The tl;dr of this is that when a library registers itself as a global, you can just use that global in :require
now: (require '["JSConfetti" :as confetti])
.
Of course, none of this happens in isolation. I’m deeply grateful to the community and the sponsors who make this work sustainable: Clojurists Together, Roam Research, Nextjournal, Nubank, and many other companies and individuals. Every bit of support means I can keep refining these tools, fixing edge cases, and thinking about the long-term direction.
Here are updates about the projects/libraries I've worked on in the last two months in detail.
babashka: native, fast starting Clojure interpreter for scripting.
1.12.2
@(promise)
anymore when you spawn an httpkit server, for example. For futures and agents, bb uses a thread pool that spawns daemon threads, so that pool isn't preventing an exit. This behavior is similar to clojure -X
. You can get back the old behavior where bb always forced an exit and ignored running non-daemon threads with --force-exit
.clojure.test/*test-out*
to same print-writer as *out*
in nREPL serverCompiler/demunge
clojure.lang.TaggedLiteral/create
java.util.TimeZone/setDefault
println-str
(.getContextClassLoader (Thread/currentThread))
should be able to return results from babashka classpathdeps.clj
to 1.12.2.1565
*warn-on-reflection*
during load{string,reader}
(same as JVM Clojure) so can load code in other than than the main threadcheshire.generate/{add-encoder,encode-str}
6.8.0
1.3.0
1.21.2
fs
to 0.5.7
cheshire
to 6.1.0
SCI: Configurable Clojure/Script interpreter suitable for scripting
println-str
let
body:param-tags
on qualified instance method*suppress-read*
load-reader
*loaded-libs*
is now the single source of truth about loaded libs*warn-on-reflection*
and bind them during load-string
etc. such that set!
-ing then in a future
works.set!
syntax in CLJSmerge-opts
with :bindings
+ deprecate :bindings
(replaced by :namespaces {'user ...}
)clj-kondo: static analyzer and linter for Clojure code that sparks joy.
symbol
accepting var1.10.3
is the minimum clojure version:inline-def
with nested deftest
inline-configs
config.edn
in a git-diff-friendly way (@lread):locking-suspicious-lock
false positives:condition-always-true
false positives:locking-suspicious-lock
: report when locking is used on a single arg, interned value or local object:unresolved-protocol-method
. See docs (@emerson-matos)clojure.string/replace
and partial
as replacement fn:condition-always-true
check. (@NoahTheDuke)schema.core/defprotocol
(@emerson-matos)str
0.10.47
:deprecated-namespace
for .cljc
namespacesclerk: Moldable Live Programming for Clojure
squint: CLJS syntax to JS compiler
while
didn't compile correctlyclojure.string/includes?
ClassCastException
in statement function when passed Code records:with
option in require, e.g. :with {:type :json}
not=
as functionrandom-uuid
(@rafaeldelboni)[:$ ...]
tagscittle: Execute Clojure(Script) directly from browser script tags via SCI
globalThis
js deps (@chr15m). See docs.(.-foo-bar {})
now behaves as {}.foo_bar
, i.e. the property or method name is munged.cjohansen/dataspex
plugin (@jeroenvandijk)goog.string/format
(@jeroenvandijk)(set! #js {} -a 1)
CLJS syntax (by bumping SCI)dev
folder of the distribution + a dev/scitte.cljs-devtools.js
moduleedamame: configurable EDN and Clojure parser with location metadata and more
*suppress-read*
: :suppress-read
sci.configs: A collection of ready to be used SCI configs.
nbb: Scripting in Clojure on Node.js using SCI
:responses
key with raw responsesfs - File system utility library for Clojure
cherry: Experimental ClojureScript to ES6 module compiler
not=
is now a functionCLI: Turn Clojure functions into CLIs!
:repeated-opts
option to enforce repeating the option for accepting multiple values (e.g. --foo 1 --foo 2
rather than --foo 1 2
)deps.clj: A faithful port of the clojure CLI bash script to Clojure
CLJ_JVM_OPTS
for downloading tools jar.pod-babashka-fswatcher: babashka filewatcher pod
sci.nrepl: nREPL server for SCI projects that run in the browser
"session-closed"
to close op replypod-babashka-go-sqlite3: A babashka pod for interacting with sqlite3
http-server: serve static assets
:not-found
option for handling unfound files. The option is a function of the request and should return a map with :status
and :body
.Contributions to third party projects:
specter: Clojure(Script)'s missing piece
clojure-test-suite: Dialect-independent tests for clojure.core, and others, focused on characterizing how Clojure JVM behaves so that other dialects to reach parity.
These are (some of the) other projects I'm involved with but little to no activity happened in the past month.
Published: 2025-08-05
Tagged: clojure oss updates