Babashka 1.13.220 gets FFI

Today babashka 1.13.220 is released, with a new babashka.ffi namespace for calling C libraries directly from Babashka scripts. The babashka.ffi library is also available as a standalone library for JVM Clojure, so you can use it in your Clojure projects as well. Note that the API is still experimental, although no changes are currently planned. It just needs more exposure and your feedback :). Here's a small demo.

Calling C

This example loads libz from your system and requests the version.

(require '[babashka.ffi :as ffi :refer [defcfn]])

(def zlib (ffi/load-system-library "z"))
(def zlib-version (ffi/cfn zlib "zlibVersion" [] :string))

(zlib-version)
;;=> "1.3.1"

This example loads an OS-specific library for doing math:

(ffi/load-library
 {:mac "libm.dylib"
  :linux "libm.so.6"
  :windows "ucrtbase.dll"})

(defcfn cos "cos" [:double] :double)
(defcfn pow "pow" [:double :double] :double)

(cos 0.0)      ;;=> 1.0
(pow 2.0 10.0) ;;=> 1024.0

To get a feeling for how to use it in larger, non-trivial projects, read the library guide. Some of the API decisions like defcfn are clearly inspired by coffi, so I want to thank Joshua Suskalo for leading the way with his excellent library. But babashka.ffi is not simply a copy of coffi. It does a few things differently. You can provide an explicit library (or a function or delay that resolves to one) to defcfn for example. Also it has a place concept (inspired by Specter's paths) that efficiently lets you read from and write to structs and unions. Like coffi, babashka.ffi builds on java.lang.foreign and makes you manage memory explicitly through arenas. One benefit of this is that you'll get exceptions rather than segfaults that tear down your REPL and you can use with-open to release allocated memory.

Install

To use babashka.ffi and libraries that build on it, you have to use a dynamically linked version of babashka. On Mac and Windows this was always the default. On Linux, the static binary was preferred historically since it did not depend on your system's libc and zlib. In this release we flip this default to a mostly-static binary: all the shared C libraries that babashka needs are statically linked, and glibc is the only dynamically linked part. The aarch64 binary, although it carries -static in its name, was already built this way. Babashka on Linux is built in a container that pins the glibc version to the lowest one possible so it should work on all mainstream LTS versions of Linux today. If you still prefer the fully static binary, you can use the install script with the --static flag. If you use a package manager or a GitHub Action to install babashka, it may not yet be up to date with this new policy. If that is the case, feel free to open an issue at the babashka GitHub repo and I'll reach out to get this fixed. Meanwhile you can install babashka using the installer script on GitHub to a temporary directory to get a second installation of babashka with FFI enabled:

$ curl -sLO https://raw.githubusercontent.com/babashka/babashka/master/install
$ bash install --dir /tmp/bb-test
$ /tmp/bb-test/bb -e "(require '[babashka.ffi :as ffi]) (ffi/load-system-library \"z\")"

The installer script probes your system for the supported glibc version and falls back to the fully static version when necessary.

Demos

To validate the design of babashka.ffi, I built a few shiny demos:

  • pacman.clj: pac-man with the classic ghost personalities (requires raylib)
  • doom.clj: a raycaster with textures and sprites (requires raylib)
  • helitorus.clj: a helix around a torus (requires raylib)
  • gtk4.clj: a native GTK 4 window rendering from an atom
  • portaudio.clj: an arpeggio through a realtime audio callback
  • python.clj: embedded CPython calling back into Clojure
pac-man running in babashka through babashka.ffi and raylib

A one-liner to try these demos:

$ bb -e '(load-string (slurp "https://raw.githubusercontent.com/babashka/ffi/main/examples/pacman.clj"))'

FFI-based libraries

To validate the design of babashka.ffi even more, a couple of new libraries were born. These libraries mostly resemble existing pods but now use FFI to fulfill similar use cases.

One cool thing you could not do with a pod before is defining a Clojure function in SQLite:

(require '[babashka.sqlite :as sq])

(sq/with-conn [db nil]
  (sq/create-function! db "initials"
    (fn [s] (apply str (map first (clojure.string/split s #" ")))))
  (sq/query db ["select initials(?) i" "gerald jay sussman"]))
;;=> [{:i "gjs"}]

Tasks: :exec-fn composition

This release also has some really nice task improvements: :exec-fn tasks now compose through :depends. A task can depend on another CLI task, and the dependency's options parse, coerce and show up in --help and shell completion:

{:tasks
 {compile {:exec-fn build/compile-sources
           :cli {:spec {:release {:coerce :boolean}}}}
  jar     {:depends [compile]
           :exec-fn build/jar}}}
$ bb jar --help
...
Inherited options:
  --release

Also you can now directly provide :exec-args on a task:

{:tasks
 {deploy {:exec-fn deploy/run
          :exec-args {:env "staging"}}}}

A :cmd tree can now be provided through a var, whose namespace is loaded on demand:

{:tasks
 {cli {:cmd my.project.cli/commands}}}

AI disclosure

While developing FFI and while validating the design through examples and writing libraries, I have made use of LLM assistance.

Wrapping up

Hope you'll like these new features!

The full changelog can be found here.

Published: 2026-08-31

Tagged: clojure tasks babashka ffi

Babashka tasks with automatic help and completions

Babashka tasks is a project task manager that is part of babashka since May 2021. It's a practical way to manage a Clojure or other software project. Just invoking bb tasks gives you an overview of everything that is important to developers of this project. E.g. launching a REPL, bumping and releasing new versions, etc.

Example bb.edn:

{:tasks
 {dev   {:doc "Start the dev system"
         :task (clojure "-M:dev")}

  test  {:doc "Run the tests"
         :task (clojure "-M:test")}

  build {:doc "Build an uberjar"
         :depends [test]
         :task (clojure "-T:build uber")}}}
$ bb tasks
The following tasks are available:

dev    Start the dev system
test   Run the tests
build  Build an uberjar

The bb build task runs test first because it :depends on it.

Before: exec and -x

Babashka CLI is a command line parsing library that comes bundled with babashka. Babashka tasks already had some integration with it before, through the exec function. The exec function auto-resolve a var symbol and that var can carry a :org.babashka/cli specification.

bb.edn:

{:paths ["bb"]
 :tasks {dev {:doc "Start the dev system"
              :task (exec 'tasks/dev)}}}

bb/tasks.clj:

(ns tasks
  (:require [babashka.tasks :refer [clojure]]))

(defn dev
  {:org.babashka/cli {:spec {:port {:coerce :int
                                    :default 8080}}}}
  [opts]
  (clojure "-X:dev" opts))
$ bb dev
dev system on port 8080

$ bb dev --port 3000
dev system on port 3000

Babashka's -x flag already invoked that function by its qualified name:

$ bb -x tasks/dev
dev system on port 8080

$ bb -x tasks/dev --port 3000
dev system on port 3000

Since a couple of months, Babashka CLI provides automatic help and completions. Babashka tasks now uses those when configured with either :exec-fn or :cmd.

Opting in with :exec-fn

To opt-in to that new behavior, the dev task becomes:

bb.edn:

{:paths ["bb"]
 :tasks {dev {:exec-fn tasks/dev}}}

bb/tasks.clj:

(ns tasks
  (:require [babashka.tasks :refer [clojure]]))

(defn dev
  "Start the dev system"
  {:org.babashka/cli {:spec {:port {:coerce :int
                                    :default 8080
                                    :desc "HTTP port"}}}}
  [opts]
  (clojure "-X:dev" opts))
$ bb dev --help
Usage: bb dev [options]

Start the dev system

Options:
      --port  HTTP port (default: 8080)
  -h, --help  Show this help

The function's docstring also becomes the task description in bb tasks:

$ bb tasks
The following tasks are available:

dev  Start the dev system

Task explosion

One thing I noticed in some projects is that during their lifecycle, tasks tended to get copied when they need another option.

$ bb tasks
The following tasks are available:

dev                              Start the dev system
dev:with-transactor              Start the dev system with a transactor
dev:with-transactor:with-nrepl   Start the dev system with a transactor and nREPL

I now think that this is a "task smell" and this should be only one task with command line options:

bb/tasks.clj:

(defn dev
  "Start the dev system"
  {:org.babashka/cli {:spec {:port       {:coerce :int
                                          :default 8080
                                          :desc "HTTP port"}
                             :transactor {:coerce :boolean
                                          :desc "Start a transactor"}
                             :nrepl      {:coerce :boolean
                                          :desc "Start an nREPL server"}}}}
  [opts]
  (clojure "-X:dev" opts))
$ bb dev --help
Usage: bb dev [options]

Start the dev system

Options:
      --port        HTTP port (default: 8080)
      --transactor  Start a transactor
      --nrepl       Start an nREPL server
  -h, --help        Show this help

$ bb dev --transactor
dev system on port 8080 transactor=true nrepl=false

Shell completions

To enable completions, you can use bb org.babashka.cli/completions snippet --shell <shell> for your specific shell.

E.g. for zsh, we do this by adding this to ~/.zshrc after compinit:

source <(bb org.babashka.cli/completions snippet --shell zsh)

For Bash, fish, PowerShell, and Nushell, see Completions in the Babashka CLI README.

After doing that, task name completion includes descriptions on auto-complete:

$ bb <TAB>
dev  -- Start the dev system

Task specific option completions includes descriptions:

$ bb dev <TAB>
--help  -h  -- Show this help
--port      -- HTTP port

You can call the completions command directly to inspect its output without a shell, e.g. for debugging:

$ bb org.babashka.cli/completions complete --shell zsh -- dev ''
--port	HTTP port
--help	Show this help
-h	Show this help

Restricting values with :enum

Use :enum to restrict an option to a fixed set of values:

(defn dev
  "Start the dev system"
  {:org.babashka/cli {:spec {:port {:coerce :int
                                    :default 8080
                                    :desc "HTTP port"}
                             :env  {:desc "Environment"
                                    :enum ["dev" "staging" "prod"]
                                    :default "dev"}}}}
  [opts]
  (clojure "-X:dev" opts))

The help output lists the allowed values:

$ bb dev --help
Usage: bb dev [options]

Start the dev system

Options:
      --port  HTTP port (default: 8080)
      --env   Environment (one of: dev, staging, prod) (default: dev)
  -h, --help  Show this help

The value is also validated:

$ bb dev --env qa
Error: Invalid value for option --env: qa. Expected one of: dev, staging, prod

Usage: bb dev [options]

Run "bb dev --help" for more information.

Shell completion also uses the :enum values:

$ bb dev --env <TAB>
dev  prod  staging

Commands

Add commands to a task with :cmd:

bb.edn:

{:paths ["bb"]
 :tasks {dev {:exec-fn tasks/dev}

         db  {:doc "Manage the database"
              :cmd {"migrate" {:exec-fn tasks/db-migrate}
                    "seed"    {:exec-fn tasks/db-seed}}}}}

Each leaf uses :exec-fn to point to a function:

bb/tasks.clj:

(defn db-migrate
  "Run pending migrations"
  {:org.babashka/cli {:spec {:env {:desc "Environment"
                                   :enum envs
                                   :default "dev"}}}}
  [{:keys [env]}]
  (println "migrating" env))

(defn db-seed
  "Seed the database with fixtures"
  {:org.babashka/cli {:spec {:env {:desc "Environment"
                                   :enum envs
                                   :default "dev"}}}}
  [{:keys [env]}]
  (println "seeding" env))

A top-level :exec-fn can handle bb db. When no command is specified:

$ bb db
No command given.

Automatic help lists the commands and provides separate help for each:

$ bb db --help
Usage: bb db [options] <command>

Manage the database

Commands:
  migrate  Run pending migrations
  seed     Seed the database with fixtures

Options:
  -h, --help  Show this help

Run "bb db <command> --help" for more information on a command.

Shell completion includes commands and option values:

$ bb db <TAB>
migrate  -- Run pending migrations
seed     -- Seed the database with fixtures

$ bb db migrate --env <TAB>
dev  prod  staging

CLI settings with :cli

Use :cli for Babashka CLI settings, such as a help epilog:

bb.edn:

{:paths ["bb"]
 :tasks {dev {:exec-fn tasks/dev}

         db  {:doc "Manage the database"
              :cli {:epilog "Migration code is in resources/migrations."}
              :cmd {"migrate" {:exec-fn tasks/db-migrate}
                    "seed"    {:exec-fn tasks/db-seed}}}}}
$ bb db --help
Usage: bb db [options] <command>

Manage the database

Commands:
  migrate  Run pending migrations
  seed     Seed the database with fixtures

Options:
  -h, --help  Show this help

Run "bb db <command> --help" for more information on a command.

Migration code is in resources/migrations.

For tasks that require code outside of bb.edn, :cli may contain a fully qualified var symbol:

bb.edn:

{:paths ["bb"]
 :tasks {dev {:exec-fn tasks/dev}

         db  {:doc "Manage the database"
              :cli tasks/db-cli
              :cmd {"migrate" {:exec-fn tasks/db-migrate}
                    "seed"    {:exec-fn tasks/db-seed}}}}}

bb/tasks.clj:

(defn- report-error
  [{:keys [msg]}]
  (binding [*out* *err*]
    (println "db:" msg))
  (System/exit 1))

(def db-cli
  {:epilog "Migration code is in resources/migrations."
   :error-fn report-error})
$ bb db migrate --env qa
db: Invalid value for option --env: qa. Expected one of: dev, staging, prod

Top-level :cli options in :tasks apply to every CLI task:

{:paths ["bb"]
 :tasks {:cli tasks/db-cli
         ...}}

Multi-line docs

A :doc value may be a vector of lines:

bb.edn:

db {:doc ["Manage the database"
          "Migrations are applied in order and are idempotent."]
    :cmd {"migrate" {:exec-fn tasks/db-migrate}
          "seed"    {:exec-fn tasks/db-seed}}}
$ bb db --help
Usage: bb db [options] <command>

Manage the database
Migrations are applied in order and are idempotent.

Commands:
  migrate  Run pending migrations
  seed     Seed the database with fixtures

Options:
  -h, --help  Show this help

Run "bb db <command> --help" for more information on a command.

The bb tasks overview prints only the first line:

$ bb tasks
The following tasks are available:

dev  Start the dev system
db   Manage the database

Availability

The task integration is available in babashka 1.13.219. The Babashka CLI features it builds on are in 0.12.85:

org.babashka/cli {:mvn/version "0.12.85"}

Closing remarks

I hope you'll enjoy these new additions to bb tasks! The new task keys should be considered experimental and may change in a future version of babashka, depending on feedback from the community.

Published: 2026-07-27

Tagged: clojure tasks cli babashka

OSS updates May and June 2026

In this post I'll give updates about open source I worked on during May and June 2026.

To see previous OSS updates, go here.

Sponsors

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.

gratitude

Current top tier sponsors:

Open the details section for more info about sponsoring.

Sponsor info

If you want to ensure that the projects I work on are sustainably maintained, you can sponsor this work in the following ways. If you work for a company that uses my OSS, please ask your employer, that would be even better. Thank you!

Updates

A lot happened in the past two months! Not just coding but also...

Babashka Conf 2026 and Dutch Clojure Days

Three years after the initial installment, Babashka Conf 2026 happened on May 8th at the OBA Oosterdok library in Amsterdam, with David Nolen, primary maintainer of ClojureScript, as our keynote speaker. Thanks to our sponsors Nubank, Exoscale, Bob, Flexiana and Itonomi, to Wendy Randolph for hosting, and to all the speakers, volunteers and attendees who made it such an inspiring day. You can watch all the videos here. Thanks to Ray for recording! The day after, Dutch Clojure Days 2026 rounded out a full weekend of Clojure in Amsterdam, where I did a presentation about ClojureScript and async/await. The video of that is hopefully coming soon.

Babashka Conf 2026 speakers and organizers

Babashka Conf 2026. From left to right: David Nolen, Jen Myers, Adrian Smith, Josh Glover, Rahul Dé, Arne Brasseur, Christoph Neumann, Timo Kramer, Jynn Nelson, Wendy Randolph.

Upcoming: babashka workshop at the Clojure/conj

I'm pleased to announce that Rahul Dé and I will be hosting a babashka workshop at the Clojure Conj 2026. The workshop will showcase various use cases of babashka. This hands-on workshop covers the whole lifecycle of a babashka tool, from a quick script to a published, installable CLI app. We assume you know the basics of Clojure and won't explain the language itself. Topics include:

  • Setting up your dev environment
  • Managing projects with babashka tasks (bb.edn)
  • A tour of built-in libraries (fs, process, http-client, and more)
  • Writing and running tests
  • Building a CLI with subcommands and automatic help
  • Programming a terminal UI (TUI)
  • Producing a small web app
  • Publishing via GitHub or as an installable tool with bbin

Every concept comes with an exercise, building toward one culminating CLI app. There will be lots of interaction and fun!

Blog posts

Besides this update I published two blog posts in the past two months:

and a ClojureScript reference on async functions:

Projects

Babashka CLI got the most attention this cycle. I added automatic --help generation for dispatch-based CLIs and shell tab completion for bash, zsh, fish, PowerShell and Nushell. There's a dedicated post with a "build your own git" walkthrough linked above. I also made Babashka CLI Squint compatible, so CLIs built with it run on Node.js and in the browser, published as the @babashka/cli npm package. Also ClojureDart support for Babashka CLI got added.

Squint saw a large amount of work that kept going right into early July: a browser nREPL, dynamic vars and binding that survive across separately-compiled ESM modules, an EDN reader, cached lazy seqs, defrecord and a wide set of core protocols, and a big compatibility push to make it pass jank's clojure-test-suite. Replicant now runs on Squint too. I added key diffing to Reagami and did some benchmarks, showing that Reagami on squint performs in the ballpark of React. The benchmark also shows that Replicant on Squint performs even a tad better than on ClojureScript. Not that this makes a huge difference in practice, but it's nice to validate the idea that Squint, for typical apps, can be a valid CLJS replacement while not giving up that much in terms of Clojure features.

A security issue in SCI deserves a callout. A string type-hint could bypass the :classes allowlist and statically initialize any class on the classpath at analysis time. If you sandbox untrusted code with SCI, upgrade to 0.13.53. ClojureDart support and fine-grained interop control (which was needed for cljd support since it has no reflection) also got added. You can now make REPLs for your mobile apps!

Since porting was a theme these past months, I'll mention another one: babashka.fs now runs on Node.js via ClojureScript and squint, published as the @babashka/fs npm package.

Here are some highlights per project. See each project's CHANGELOG.md for the full list.

  • babashka CLI: Turn Clojure functions into CLIs!

    • Automatic --help generation for dispatch CLIs, plus shell completions for bash, zsh, fish, PowerShell and Nushell (#112, #24, #95). I wrote a full post on it with a "write your own git" walkthrough: babashka CLI: automatic --help and shell completions
    • Exposed the underlying building blocks so you can roll your own custom CLI parsing: parse-opts*, coerce-opts, validate-opts, apply-defaults, table->tree
    • dispatch now accepts a tree directly (as returned by table->tree), and subcommand order is preserved in printed help and completions
    • Squint support and a new @babashka/cli npm package
    • ClojureDart support (#182)
    • opts->table accepts :columns to override the auto-detected columns (#148, thanks Jan Seeger)
    • Better error messages: negation errors now name the base option, --no-foo on a non-boolean option errors instead of silently coercing, and :edn :coerce now requires an explicit value (#166, #174)
    • Thanks to @lread for a lot of documentation review and general maintenance during this cycle
    • Full changelog
  • Squint: CLJS syntax to JS compiler

    • Browser nREPL support landed, followed by a number of REPL/nREPL fixes: #815 (str wrapping tripping esbuild), #819 (macro changes not picked up in watch mode), #820 (:macros option ignored from JS callers) and #832 (nREPL server hanging on advertised-but-unimplemented ops)
    • The CLI now gets its --help, usage and error handling from babashka.cli's dispatch, plus shell tab completion
    • Dynamic vars and binding now work via a mutable box, safe across separately-compiled ESM modules; syntax-quote resolves symbols through the current namespace and aliases like Clojure. defprotocol got :extend-via-metadata support.
    • reify added
    • clojure.walk added
    • Added squint.edn/clojure.edn with a ~300-line EDN reader
    • Printing is now done through *print-fn*, print, pr and with-out-str, like CLJS
    • Lazy seqs are now cached instead of recomputed on every consumption, matching CLJS's chunked-seq behavior
    • A big push for compatibility with jank's clojure-test-suite: dozens of core functions (sorted-map, hash-map, subvec, pop, merge, keys/vals, peek, transducers, = on dates/regexes/lazy seqs, and more) now throw or behave exactly like CLJS instead of the old loose JS semantics, alongside full built-in cljs.test support
    • #771: dead-code elimination for varargs/multi-arity functions, now emitted via ... spread
    • Replicant support landed, with an example
    • Added defrecord, record? and the IRecord marker protocol. Records store their fields as own string-keyed properties and implement the map-facing protocols, so keyword lookup, keys, seq, assoc, conj and = all work through the regular core functions; the generated implementations are shared runtime functions imported only by files that use defrecord
    • Added a large set of core protocols so custom types participate in the standard functions: ILookup, IAssociative, IMap, ICounted, ICollection, IEquiv, ISet, the transient protocols, and IAtom/IDeref/IReset/ISwap/IWatchable (so a reagent-style reactive atom can be a plain deftype)
    • Compile-time namespace resolution: cljs.analyzer.api/resolve now sees vars of built-in library namespaces like clojure.string, plus :squint/compile-time forms and fixes for macro self-use
    • Full changelog
  • clj-kondo: static analyzer and linter for Clojure code that sparks joy.

    • NEW: macros from source. A defmacro (plus any supporting defn/defn-/def) tagged with {:clj-kondo/macroexpand-hook true} is automatically extracted into .clj-kondo/ and registered as a :macroexpand hook on the next run. See doc/hooks.md
    • Support for async/await in ClojureScript: bumped built-in CLJS analysis to 1.12.145 and added the :await-without-async-fn and :misplaced-async-metadata linters
    • #2822: NEW linter :alias-same-as-ns, warns when an alias equals the namespace it aliases (default :off) (@tomdl89)
    • #2807: NEW linter :conditional-build-up, warns on successive (if pred (assoc m ...) m) rebinding and suggests cond-> (default :off) (@walber-araujo)
    • #2062: NEW linter :if-x-x-y, suggests (or x y) instead of (if x x y) (default :off) (@jramosg)
    • #2818: fix :redefined-var false positive across files declaring the same namespace
    • #2814: fix :protocol-method-arity-mismatch false positive for definterface declaring the same method with multiple arities (@jramosg)
    • #2817: warn on recur inside a vector, map or set literal, since recur is never in tail position there
    • #2854: fix :invalid-arity false positive when an inner binding or fn param shadows a local function name (@yuhan0)
    • Performance work on the rewrite-clj parser and analysis internals: efficient get-in/select-keys, faster sexpr, leaner node allocation (@alexander-yakushev)
    • Deprecation notice: 2026.05.25 is the last release to include the clj-kondo LSP server and VS Code extension; use clojure-lsp instead, which embeds clj-kondo
    • Queued for the next release: early support for the Clojure 1.13 map destructuring keys (:keys!/:syms!/:strs!), including inferring required keys and reporting them at call sites (#2870)
    • Full changelog
  • SCI: Configurable Clojure/Script interpreter suitable for scripting

    • ClojureDart support, with a Flutter REPL example
    • Instance/static method and field overrides plus a :closed allowlist for :classes, giving fine-grained control over host interop; see the interop control docs. Also 1.6x faster instance-method interop on babashka
    • Security fix (sandbox escape): a string type-hint (e.g. ^"some.Class" x) bypassed the :classes allowlist, loading and static-initializing any class on the classpath at analysis time. Only affects sandboxing of untrusted code via :classes; upgrade to 0.13.53
    • Add an :interrupt-fn option: a zero-arg function called on every interpreted fn entry, so host code can interrupt or cancel a running SCI eval (thanks @whilo)
    • Add sci.interrupt/interrupt! to throw an interrupt that sandboxed try/catch cannot catch, and gate finally and the regex functions (re-matches/re-find/re-seq, JVM) through :interrupt-fn too, closing off ways to mask an interrupt and escape the sandbox #1044
    • Fix copy-var incorrectly marking a function as inlined when its unqualified name collided with a clojure.core/cljs.core inlined var (e.g. a custom get), silently breaking with-redefs (@verberktstan)
    • Fix cross-namespace defrecord/deftype type symbol resolution via alias (e.g. (instance? r/Foo x)), fixing nbb#410
    • Fix a self-require (a namespace requiring itself) being reported as a cyclic load dependency
    • Full changelog
  • fs: file system utility library for Clojure

    • Released 0.5.34 with Node.js support (#265): fs now runs on Node.js via ClojureScript and Squint / JavaScript, published as the @babashka/fs npm package. Most functions are supported. The JVM behavior is the reference implementation so all operations are synchronous, and the glob syntax is reimplemented from scratch to match the JVM. File times are BigInt nanoseconds to preserve sub-millisecond precision. zip is left out since Node.js has no native support for it
    • Added spit and slurp on both the JVM and Node.js
    • exec-paths returns [] when PATH is unset or blank instead of throwing
    • @lread did a thorough review pass making the return values of copy, copy-tree, delete-tree, zip/unzip, gunzip and the setters explicit and documented/tested (#197)
  • Babashka: native, fast starting Clojure interpreter for scripting.

    • Working towards a new release integrating all the newest updates in Babashka CLI and babashka.fs. Most importantly I'm working on autocompletions added for tasks defined in bb.edn.
    • #1979: fix with-redefs on copied vars (e.g. org.httpkit.client/get) incorrectly treated as inlined
    • Add org.jline.keymap.BindingReader for reading key bindings in terminal applications, completing the input side of the bundled JLine API
    • #1982: add clojure.lang.ChunkedCons, clojure.lang.APersistentVector$SubVector, clojure.lang.ArraySeq, clojure.lang.PersistentVector$ChunkedSeq, java.util.AbstractCollection and java.util.Queue to :instance-checks (@paintparty)
    • Added a terminal tetris example (examples/tetris.clj) built on JLine's Display and AttributedString, showing off the new terminal APIs
    • Full changelog
  • Reagami: A minimal zero-deps Reagent-like for Squint and CLJS

    • Added keyed reconciliation (#40): support :key on children for stable node identity, so diffing reuses nodes instead of recreating them
    • Fixed CLJS :lite-mode compatibility and added it to CI (#41)
    • Added a benchmarks page comparing reagami against CLJS React wrappers and React-free solutions, with mermaid charts to visualize the results (#42, #43)
    • Expanded the README with an ADR on the unkeyed reconciliation algorithm
  • Cream: Clojure + GraalVM Crema native binary

    • I was finally able to reproduce an issue with core.async and filed this upstream
    • Once this is fixed I'm going to consider crema more seriously and play with the thought that this could be a substrate for "Babashka next".
  • html: Html generation library inspired by squint's html tag

    • Fixed inline style maps emitting a literal \n between declarations via pr-str, which produced invalid CSS and dropped every declaration after the first (@cycl1st)
    • Only render a map attribute value as CSS when the key is style; other map-like values (e.g. records) now render via str (@telekid)
    • Fixed a symbol-valued attribute resolving to its runtime value instead of its literal name
  • Edamame: configurable EDN and Clojure parser with location metadata and more

    • Added ClojureDart support (non-indexing plain readers matching tools.reader, zero-literal parsing fix, and more)
    • With :auto-resolve-ns, bare syntax-quoted symbols now resolve to the current namespace, matching Clojure's behavior
  • Neil: A CLI to add common aliases and features to deps.edn-based projects

    • #261: neil dep upgrade now upgrades unstable deps (e.g. release candidates) to a newer unstable version when no newer stable version exists
    • Added a README note on brew trust for users who installed neil before Homebrew introduced tap trust
    • The next neil version will make use of the new Babashka CLI features which is already prepared in a PR
  • Nbb: Scripting in Clojure on Node.js using SCI

    • #410: fixed a regression, introduced by the async/await work in #408, where a defrecord/deftype type symbol referenced through a namespace alias (e.g. (instance? r/Foo x)) failed to resolve
  • deps.clj: a faithful port of the clojure CLI bash script to Clojure

    • As always, catching up with the most recent Clojure CLI versions
  • Pod-babashka-gozxing: a babashka pod for QR code and barcode decoding/encoding, backed by gozxing

    • Initial release 0.0.1, installable via the pod registry
  • Graal-build-time: initialize Clojure classes at build time for GraalVM native-image

    • #55: munge package names for namespaces with special characters

Contributions to third party projects:

  • ClojureScript: documented the async/await support from last cycle on the ClojureScript site, including an enhanced reference (#423, #424)
  • Nexus: a data-driven state management library by Christian Johansen. I ported the core engine and test suite to run under squint and added a cljs test runner alongside the existing kaocha setup, so both babashka and squint stay covered in CI (#15, #16, merged)
  • Replicant: a data-driven DOM rendering library by Christian Johansen. I made Replicant itself run under Squint (converting dom.cljs to .cljc, adjusting core.cljc for portability), added babashka/squint test runners and wired them into CI, and fixed a multi-root render bug under squint by switching DOM state tracking to a node-map (#71, #72, merged)

Other projects

These are (some of the) other projects I'm involved with but little to no activity happened in the past two months.

Click for more details

Published: 2026-07-06

Tagged: clojure oss updates

Archive