NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Go modules · #2518 by repository stars
Last release today
06 Oct 2026
Ships fairly regularly
a new release about every 9 days
Some releases are documented
notes for 11 of 29 stable releases
Nothing withdrawn
no release was ever pulled
5 years old
115 releases · first in 2021
One column per quarter.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
v1.13.0 is the largest range let-go has shipped, 179 PRs since v1.12.2, and much of it came from running real Clojure libraries and watching where the
v1.13.0 is the largest range let-go has shipped, 179 PRs since v1.12.2, and much of it came from running real Clojure libraries and watching where they broke.
Many thanks to @mparrett @nnunley and @abogoyavlensky 🤝
Two first-time contributors landed their first PRs! @dharmatech fixed Plan 9 key reads hanging on terminal resize. @jasalt added a CompilerContext accessor so a host nREPL server shares the application's live compiler state, and fixed test/run-tests throwing when it restored the namespace. Thank you!
Breaking: The opcode set changed (a new unchecked-arithmetic trio) and compressed bundles moved to format version 3, so an older lg won't load .lgb files built by this release. Rebuild your bundles.
See Bundle format and compatibility breaks below before you upgrade anything that ships .lgb files.
Real-world Clojure libraries drove most of this: integrant, ragtime, clojure.test.check, and clojure.data.priority-map each exposed a specific gap.
clojure.test, ported for real. Report, assert, fixture, and runner contract replace the partial namespace that shipped before. #863[x & more] now binds nil, not (). The old behavior made a destructured empty rest arg truthy, which hung weavejester/dependency's reachability loop, and any (if-let [[x & xs] s] ...) pattern, on a three-element chain. #898Thread/currentThread and format's %n. Thread/currentThread resolves to the calling scope dressed as a java.lang.Thread, so .isInterrupted means "was my scope cancelled," and the cooperative-cancellation idiom ragtime uses compiles. format honors Java's %n instead of erroring. #901*unchecked-math* is live. +, -, *, inc, and dec rewrite to their wrapping forms under the binding instead of being silently ignored. #839Math/* and friends. Long/bitCount, Integer/MAX_VALUE, Double/NaN, and the rest are pinned to JVM semantics rather than Go's. Java rounds -2.5 to -2; Go doesn't. #840(. obj member ...) interop forms compile, not just .member sugar. #841*data-readers*, honored by read-string, load-string, ordinary compilation, and child eval contexts. #770clojure.edn/read-string reads data, not code. ^{:a 1} x attaches metadata instead of yielding (with-meta ...), #{} reads as a set, interior #_ no longer errors on an odd map, and duplicate keys are rejected. #854clojure.string/split follows Pattern.split. Trailing empty fields are dropped, it returns vectors instead of lists, and it takes a limit arity now. split-lines was added too. #843reify accepts Object overrides: toString, hashCode, equals. #762nil through map destructuring instead of {}. #760var special form reaches private vars across namespaces; bare qualified-symbol references stay privacy-enforcing. #756json/write-json writes string map keys as text, not as their EDN reading. {"key" "value"} no longer emits {"\"key\"":"value"}. #820line-seq surfaces read errors instead of silently ending the sequence. #855test/run-tests restores the caller's namespace after an explicit-namespace run, instead of throwing. #798StringBuilder shim, needed for clojure.data.priority-map and similar libraries. #787clojure.repl and clojure.pprint land with a Var-metadata bundle round trip. #668clojure.data gets structural projection. #711thrown?, thrown-with-msg?, run-test-var, and run-test fill out the pre-#863 test surface. #754, #671Also: warn-on-core-shadow now fires for compiled defs (#734), tab completion no longer crashes on a cyclic refer graph (#710, #713), *out*/*err* defer to the current stdout/stderr rather than the boot-time handles (#611).
The HTTP namespace gained a handle you can hold instead of a bare ListenAndServe call, motivated by a real integrant and SQLite web app, and an nREPL hub carrying a downstream patch.
http/start, http/stop, http/wait. serve is now start plus wait, and a bad bind address throws at bind time instead of from inside the request loop. #898http/request: connect, request, and stream_read, each naming itself in the resulting error. #856http/get, http/post, or http/request call, including one blocked reading a :as :stream body. Before this, the request and its goroutine outlived the scope. #848net/listen, net/accept, net/local-address give TCP the owned-listener surface unix already had. #896scope-cancelled? lets code distinguish "woke up" from "was cancelled" after a blocking native call returns. #850eval runs in the caller's execution context, so binding and with-out-str reach the evaluated form. This is what an in-editor REPL needs to capture what a form prints, not only what it returns. #853bound-fn* conveys the binding stack correctly. An earlier fix rebuilt a bound call's context from a captured snapshot alone, which silently reverted any dynamic var the caller had bound but hadn't captured. The clojure.test port replaced that mechanism with an overlay onto a child of the caller's context, matching Clojure's push-thread-bindings, while keeping the original fix's guarantee. #852, superseded by #863:headers map without erroring. #849 / #898A let-go program now computes the same values regardless of host width.
Int is 64 bits wide on every platform, not type Int int following the platform word size. On linux/386, linux/arm, and TinyGo wasm, (bit-shift-left 1 40) used to return 0 and (* 1e9 1e9) trapped. The guarantee covers the VM's arithmetic core. Collection indexing, clojure.math's *-exact family, range bounds, and ToInt still convert through the platform int and can wrap above MaxInt32 on a 32-bit host. #859pkg/rt boundary on 32-bit hosts. Math/abs, hash, parse-long, and the *-exact family return full-width values instead of silently wrapping. The pkg/vm half of the same class of bug, collection indexing, range, get, is unfixed. #906:int as Go int64, which is also what makes lowered trees compile under TinyGo. #862xxh3 reaches TinyGo wasm for the first time, via Wrap-based adapters where the reflect-boxed generated binding couldn't go. Combined with the width fix above, a hash chain now produces byte-identical output on a TinyGo wasm build and on native amd64. #860curl | bash. #909, #877Also: SameFile stubbed for js/wasm in pkg/bundle (#629), wasm_exec.js resolved from the active Go toolchain instead of a hardcoded path (#837).
lg.compiler namespace, so a shipped lg binary drives lowering from any directory. scripts/lg-compile keeps its path, argv, and exit codes as a thin shim over it. #735--entry-frame emits a native-entry Go frame from main or -main, and no longer runs it twice. lg -c now omits only the selected entry call when it builds the bundle, so the fact that it's an entry point lives in the artifact instead of a global *compiling-aot* flag that would also skip every other namespace's runtime init. #628, fixed by #902unchecked-add, unchecked-subtract, unchecked-multiply are first-class IR and VM ops, so a hash or PRNG inner loop keeps its typed native path instead of boxing every step. This is also one half of the bundle-compatibility break below. #811recur gets its own OP_RECUR_FN op instead of piggybacking on a different lowering path (#642).Two independent, intentional breaks ship in this release. Both cause an older runtime to reject a bundle outright. That's by design, not corruption, but it isn't obvious from the error alone.
OP_UNCHECKED_ADD, OP_UNCHECKED_SUB, and OP_UNCHECKED_MUL. A .lgb built by an older lg is rejected by this one, the same trade an earlier release made when it added OP_DIV. Rejection errors now name the missing capability instead of printing a bare mask. #608, #622lg -c -z or lg -b -z. #501, #502Related bundle work:
lg -strip splits debug info into a digest-bound .lgb.debug companion instead of discarding it. A mismatched companion is rejected instead of producing misleading tracebacks, and it now composes with -w bundles too. #624, #800lg_no_http build tag drops net/http from the runtime entirely. #658ir.data stopped being compiled into the shared const pool. #838Go API and build-layout changes:
rt.SetSrc, rt.TestSrc, and so on), superseded by coreFS. No public callers were found at review time. #604pkg/cli. Root package went from eight Go files to three. Anyone importing root-package symbols moves to pkg/cli, see docs/guide/custom-lg.md. This is also what makes the interop packages self-contained enough for a third-party module to build its own lg-equivalent binary. #773api.LetGo exposes CompilerContext(), so a host nREPL server, or a similar integration, can share the running application's live compiler state instead of constructing a disconnected second one. #803build/, and lg is promoted into bin/. Scripts hard-coding the old paths need updating. #733go.mod, initially pinned to 1.26.5 (#677) and then bumped to Go 1.27, with golangci-lint pinned alongside it (#910). The manifest generator now reads the toolchain from go.mod rather than GOTOOLCHAIN=local (#916).No headline benchmark numbers ship with this release. The measurement tooling itself, bench-ratchet, needed fixing more than the runtime did for most of this range: a stale probe table produced apparent regressions that turned out to be a baseline-selection bug, not a real one. Read this section as "the direction is good," not "here is the number."
reduce's ArrayVector and Range fast paths were restored after a registrar shadowed them (#686). PreparedCall for per-element callback loops landed in some (#726) and in reduce's four fold loops (#727), then made allocation-free (#794); a reduce benchmark measured 50.5ms against 58.7ms on v1.12.2, below the old baseline rather than merely recovered toward it, though that number predates the pinned-SHA audit this project still owes itself. Cold error arms moved out of the dispatch loop (#723), the runLoop hop was removed (#730), and defer scaffolding stayed off the hot path (#719). Catalog-driven op and form-head dispatch (#612, #667), direct-call natives for the hot clojure.core surface (#613), and 222 primitives hoisted to //lg:native declarations with a per-package registrar (#639, #640). Ordinary var loads are re-emitted rather than copied (#579), and lazy var metadata was compacted (#781).
The other half of this release's perf story is the measurement infrastructure: deterministic forced rebaselining and a pre-push gate (#780), the baseline reseeded from a measurement window instead of a single point (#684), a per-release baseline frozen on every tag (#740), a machine-quiescence lease so benchmark runs don't compete with other jobs for CPU (#795), and the deterministic-metrics judge fixed to compare against the newest-provenance entry per metric instead of a global minimum across differently-aged profiles, which is what had been producing phantom 60 to 70 percent regression reports. #900
Also: per-iteration setup hoisted out of vector benchmark loops (#882), execution-entry modes distinguished in compiler benchmarks (#621), *runtime-defn-ir-cache* retained only for the current namespace (#650).
glplat merged as a platform layer with two backend implementations: GLFW/OpenGL, which is cgo-native only, and Ebitengine, which also reaches browser and Metal. Pick one with -tags glplat or -tags glplat_ebiten. The glplat namespace is present in a default, untagged build, but every native in it returns no backend registered and no graphics dependency is linked. #744internal/, and a planned rt.Display/*display* follow-up may rename natives and change Init's signature. Treat glplat as experimental. #874x/image and x/text no longer land in every default binary. #789lint.lg flags breadcrumb and devlog comments (#836), with objective comment-abuse rules R1 through R6 and a code-verbosity catalog added on top (#870).spec-evidence makes fenced code blocks in docs/specs/*.md executable, so a spec and its conformance tests can't silently drift apart. #861AGENTS.md pointing at a new docs/contributor-workflow.md. #802Also: sticky-comment lookup paginated in the perf lanes (#715, #708), the ubuntu perf capture cap lowered to 60 minutes provisionally (#878), Git integration enabled in the tracked VS Code settings (#746), the generated-module scaffolding extracted into pkg/gomod (#741), the makeplus/makes clone pinned to a SHA (#632), openspec design collateral untracked (#643).
docs/guide/os.md documents the OS namespace additions from this and earlier ranges (os/unzip, os/rename, os/delete-tree, os/absolute-path, os/canonical-path). #717docs/guide/http.md) accompanies the networking work above.known-divergences.md for consistency (#825).xxh3 limitation note was corrected. It previously described a substitution that doesn't occur. pkg/rt/interop_xxh3.go is !tinygo-gated, so a TinyGo build has no xxh3 namespace at all, rather than a different digest. #799generated.sums conflict during a rebase. #842Small, largely internal changes rolled up rather than given their own bullets: #797 (public compile-form in an example), #749/#750 (dependency-manifest refresh after path moves), #633 (lginterop round-trip moved to the expensive e2e lane), #680 (scrub GIT_DIR from compat-suite git calls), #676 (isolate compat-suite const pools per case), #556 (self-heal a drifted clojure-test-suite submodule), #614 (stage the lowered tree in an OS tempdir before copying into place), #630 (emit //go:build constraints from lginterop), #610 (alias-collision guard compares namespace names, not pointers), #784 (report every duplicate manifest entry, not just the first).
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
9c9a3d6 fix(wasm): resolve go.sum for released-binary lg -w builds
First release since v1.11.1 (2026-06-29). 126 commits in about three weeks, centered on one theme: running real Clojure libraries unchanged. Stock Hon
First release since v1.11.1 (2026-06-29). 126 commits in about three weeks,
centered on one theme: running real Clojure libraries unchanged. Stock HoneySQL
now works out of the box, backed by a round of JVM host-compat and exception-
semantics work. Beyond that: a compiler-free runtime binary, a WASI build, TCP
networking, a clearer Go-interop story, substantial AOT progress, and another
round of runtime performance improvements. There are no breaking API removals;
see the upgrade notes for observable behavior changes.
The headline: stock HoneySQL runs on let-go unchanged (#494). Getting there
closed a long tail of JVM-compat gaps that benefit any library you try next:
java.util.HashMap and ArrayDeque (#512), static method callsjava.lang exception hierarchy (#472), typed catch dispatch (#476), andfinally blocks that run on every exit path (#474).fn supports :pre/:post conditions (#458), defprotocol accepts standard(do ...) forms evaluate subform-by-subform (#470), and requirecore/* shortcut (#548).let-go has always bundled to bytecode (lg -c) and standalone binaries
(lg -b). This release hardens and extends that pipeline:
lg-runtime is a compiler-free binary that executes precompiled .lgb^double or ^long can use unboxed Go numerics inNew targets round out where compiled output can run: a WASI (wasip1)
module for any WASI host (#466), real terminal input on Plan 9 (#460, #461),
custom browser shell templates (#377), and an in-browser IR/bytecode/lowered-Go
inspector notebook (#379).
api.NewLetGo plus Def hands Go values,lginterop generates smart wrappers around Godatabase/sql end to end, and an e2e//lg:native annotation lets Go functions backStart with docs/guide/go-interop.md.
net namespace with TCP client support plus bencode encoding and framingrequest-method as a keyword (#527).update-keys, update-vals, ns-publics, find-var, get-method, and a realPersistentQueue (#447, #384, #417, #419).spit/write!,os/exec*, and function metadata (#368, #367, #371, #383).false andnil (#391).See docs/guide/usage.md, docs/guide/net.md, and
docs/guide/clojure-compatibility.md for the newly expanded guidance (#550).
Keyword/IntNo hard-breaking API changes, but a few things behave differently:
clojure.corelet-go.core and let-go.types (#410). Update requires for code thatclojure.core.false/nil, matching Clojure (#391)..lgb bundles built by older opcode sets arelg exits non-zero when a script or -e expression failsrequest-method is a keyword such as :get,trace opcode is replaced by the dynamically scoped*lg-trace* facility; recompile bundles and migrate direct trace usage (#407).Full change and contributor attribution follows.
GOOS=wasip1) module by @mparrett in #466Note truncated.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
d418eef Brew cask update for let-go version v1.11.0
Nothing published for this version
…a round of VM and compiler performance work. No breaking changes.
First release since v1.10.0 (2026-06-08). 109 commits: a browser-runtime host-decoupling arc, native-Go IR lowering across most of the Clojure-compat surface (now emitting typed cross-namespace direct calls), terminal mouse input, and a round of VM and compiler performance work. No breaking changes.
The WASM host now talks to Go through typed seams instead of ad-hoc JS shims, and the embedding page can own the shell.
-w-shell none lets the host page supply its own shell; lg-host.js is split out so a client can drive the runtime directly (#245).-w-wasm external ships the payload as a separate streaming main.wasm rather than inlining it (#246).HostWriter for *out*/*err* (#231), a HostEmitter for js/emit (#241), a KeySource for term/read-key and key-pending? (#244), and a host-bound storage seam (#315).js/url-param reads a query parameter from the launch URL, so a WASM bundle can pick up configuration from the page it's loaded on (#339).-w-host-eval exposes LetGoHost.eval, letting a JS host evaluate code in a live in-page image; it works in both main-thread and worker boot modes and is gated to host-eval bundles (#340).dvh height (#291), and an overflow guard on host.html (#277).*command-line-args* in core (#205).os/free-port returns an OS-assigned TCP port (#209).str/replace and replace-first accept a function replacement, matching Clojure (#216).alts! takes a :default for non-blocking select (#250).set-rand-seed! reseeds the runtime RNG for reproducible rand/rand-int/rand-nth/shuffle, backed by a mutex-guarded math/rand/v2 source (#341).-cpuprofile/-memprofile flags, with LG_CPUPROFILE/LG_MEMPROFILE env fallbacks for generated builds (#331).The self-AOT pipeline now lowers most of the language straight to Go.
clojure.core and lowered siblings now emit direct Go calls instead of dynamic InvokeValue, built on reified IR op-types and interface-category type classification (#310, #318, #320, #321, #322). The arc extends to hot clojure.core builtins via pkg/rt/builtins (#343), cross-package calls between lowered packages (#345), and a build-call that builds args once in callee-before-arguments order (#344).defmulti/defmethod lower to native Go (#325).:try lowering (#316), a fresh-regen nil in ir.passes.purity (#309), and empty-list () lowering to the empty-list constant rather than invoke-nil (#342).PersistentVector.pop, previously O(n) (#302).PersistentMap.Equals compares in place instead of materializing a node seq (#300).subs no longer materializes the whole string to runes (a124d2d).No breaking changes. A few behavior changes are worth knowing if you embed the runtime:
print/pr/prn/println and the term/* ANSI control ops now route through *out*, with error sites through *err* (#206, #223). Output and terminal control that previously went straight to stdout now follow whatever those vars are bound to.WithStdout/WithStderr options on NewLetGo bind those streams at construction, scoped per Run (#207).fs.writeSync output shim is retired in favor of the HostWriter seam (#231).LG_SUPPRESS_SOURCE_PATHS_WARNING are removed (#230).Full commit list below.
make test (#215)Note truncated.
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
bdab027 Brew cask update for let-go version v1.9.0
weavejester/medley (#157)make check-generated (#162)Nothing published for this version
17ad803 Add term/key-pending? non-blocking input peek
Nothing published for this version
ff2ec36 Brew formula update for let-go version v2.0.2
Nothing published for this version
Nothing published for this version
941bbd0 Update sort compatibility gates
d8543c4 Add -source-paths flag and LG_SOURCE_PATHS env var
Nothing published for this version
dd53ddb Add go-joker support to benchmark scripts and documentation
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Your coding agent can read these notes before it upgrades. Set up the MCP server →