OCaml Weekly News
Hello
Here is the latest OCaml Weekly News, for the week of April 21 to 28, 2026.
Table of Contents
- Closing Old Bugs With Tests: A Dune Contribution Story
- ocaml-wire: a Binary wire format DSL with EverParse 3D output
- valkey: modern Valkey client for OCaml 5 + Eio, on opam
- UnifiedScript_{Std,Top} - literate testing
- openrouter_api: An OpenRouter client library
- Call for presentations: Caml in the Capital II
- Old CWN
Closing Old Bugs With Tests: A Dune Contribution Story
Robin Bate Boerop announced
I recently started contributing to Dune with no prior familiarity with the code base. One thing I found rewarding was closing old issues — some filed as far back as 2018 — and I wanted to share the approach I used.
The Method: Close With a Test
Many open issues describe behaviour that has already been fixed, or behaviour that is working correctly but was never verified with a test. My approach: write a cram test that demonstrates the expected behaviour, link the PR to the issue with fixes #NNNN, and let the merge close the issue automatically.
This has several advantages over simply closing an issue with a comment like "this works now":
- It's verifiable. Anyone can read the test and see exactly what behaviour is being asserted. There's no argument about whether the issue is truly resolved.
- It prevents regressions. If the behaviour breaks again in the future, the test fails. The issue doesn't silently reopen — the CI catches it.
- It grows the test suite. Every closed issue leaves behind a test that documents a real-world scenario someone cared about.
- It respects the reporter. Someone took the time to file the issue. A test that captures their concern is a better acknowledgement than a drive-by close.
After a few weeks of contributing test PRs and bug fixes, I was given triager and then maintainer status on the project. This let me close issues directly when they were clearly resolved, and merge my own test PRs. Before that, the existing maintainers merged them on my behalf.
Honestly, I regret closing issues where I could have used this method but didn't. The method is much superior to simply adding to a GitHub discussion about the bug and then closing the issue.
Issues I Helped Close
Here's an accounting of what I helped close, grouped by what it took:
- Confirmed resolved; closed directly
These issues described problems that no longer existed on main. In some cases I confirmed this myself, in others someone else had already verified it. As a triager/maintainer I was able to close them:
- #1974 —
@alltarget doesn't interact well with(include_subdirs ...)(filed 2019) - #3173 — Can't promote into a directory starting with underscore (filed 2020)
- #3805 — No such file or directory when
DUNE_BUILD_DIRis set during test (filed 2020) - #8242 — Very slow emacs compilation buffer updates (filed 2023)
- #10360 —
executables_implicit_empty_intfexpects dune >= 2.9 but was only added in 3.0 (filed 2024)
- #1974 —
- Closed with a test PR
These issues were resolved by writing a test that demonstrates the correct behaviour. No code changes were needed — the test itself closed the issue:
- #2370 —
.hfiles copied to_buildwithout foreign stubs (filed 2019, test: #14172) - #3362 — Cannot use
%{lib:...}in theflagsstanza (filed 2020, test: #14146) - #3484 — Promoting over a running binary (filed 2020, test: #14173)
- #11110 — Libexec site install doesn't set executable bit (filed 2024, test: #14171)
- #2370 —
- Closed with a test and a code fix
These required both a test and a code change to resolve:
- #878 —
dune substadds duplicate version field in opam (filed 2018, fix: #14136) - #2445 — dune sends SIGKILL without prior SIGTERM (filed 2019, fixes: #14170, #14224)
- #3916 — Duplicate bounds in autogenerated opam files (filed 2020, fix: #14175)
- #6148 —
root_modulegenerates duplicate module definition (filed 2022, fix: #14135) - #9757 —
dune runtestdoesn't execute byte-only inline tests (filed 2024, fix: #14174) - #10707 — Missing menhir lower bound in generated opam files (filed 2024, fix: #14168)
- #11002 — opam file generation error-prone (filed 2024, fix: #14136)
- #11106 — Redundant dune version constraint in opam files (filed 2024, fix: #14175)
- #12007 —
dune build @ocaml-indexbuilds too many files (filed 2025, partially fixed with #14137) - #14089 — Incremental builds with
(wrapped (transition ...))broken (filed 2026, test: #14088, fix: #14090)
- #878 —
What I Learned
Writing tests is a good way to learn about a code base. Each test forced me to understand how a feature actually works — not the architecture in the abstract, but the concrete behaviour in specific scenarios. The understanding compounded.
Dune's cram tests make this easy. Each test is a self-contained shell script with expected output, checked into the repo as a .t file. The barrier to writing a test is low, which is exactly what you want when your goal is to close issues by testing them. This occasionally backfired by being "too easy" - when I wrote tests to which maintainers objected because I had not done so in a Dune-idiomatic way; but this was still net positive for sure.
Consistent presence matters. I contributed nearly every day over several weeks. I think that this helped maintainers feel that I was "present" - that efforts that they made to help me were investments; so they were less prone to holding back reviews.
ocaml-wire: a Binary wire format DSL with EverParse 3D output
Archive: https://discuss.ocaml.org/t/ann-ocaml-wire-a-binary-wire-format-dsl-with-everparse-3d-output/18009/1
Thomas Gazagnaire announced
I'm happy to announce the release of ocaml-wire 0.9.0 to opam!
ocaml-wire is a set of combinators to describe binary protocols in OCaml. "Binary" as in fixed-layout: sizes and shapes are mostly known up front, so you're reading fields at known offsets rather than parsing a free-form grammar (like in Menhir). There are already many good binary parser frameworks in OCaml – here's what's different in ocaml-wire:
- The central data structure is EverParse's 3D grammar. The OCaml combinators describe a 3D structure. Following Daniel's tagged final encoding approach, you build a codec that describes a struct and how to serialise/deserialise it from an OCaml value.
- Which means every description can compile down to a .3d file and from there to C via EverParse – and the generated C compiles cleanly with
-std=c99 -Wall -Werror. So you could, in theory (I haven't pushed on this too far for now), build and link a complex stack as an standalone, external C parser whose memory safety, single-pass behaviour, and conformance to the 3D spec are proven in F*(EverParse doesn't give you serialisers, unfortunately). - If you prefer, you can still use the OCaml parsers/verifiers, which have been made reasonably fast (i.e. no extra allocations on the hot path), and as they are streaming codecs you can just read or write a single field of the structure without parsing the whole thing. They also compile trivially to JavaScript via
js_of_ocaml, if you need to parse the same format in the browser.
A small example – a packet with two nibble-wide fields, a big-endian length, and a variable-size payload whose length is read from the Length field:
open Wire
type packet = { version : int; flags : int; length : int; payload : string }
let f_version = Field.v "Version" (bits ~width:4 U8)
let f_flags = Field.v "Flags" (bits ~width:4 U8)
let f_length = Field.v "Length" uint16be
let f_payload = Field.v "Payload" (byte_array ~size:(Field.ref f_length))
let codec =
let open Codec in
v "Packet" (fun version flags length payload -> { version; flags; length; payload })
[ f_version $ (fun p -> p.version);
f_flags $ (fun p -> p.flags);
f_length $ (fun p -> p.length);
f_payload $ (fun p -> p.payload) ]
Field.ref f_length is the dependent-size bit: the payload's length comes from the f_length field read earlier in the same struct. That same codec value is what you hand to Everparse.schema to get the 3D file out, and what backs Codec.get / Codec.set for streaming field access from OCaml. ocaml-wire also ships the infrastructure for generating FFI stubs and for differential testing to make sure the verified C and hand-written OCaml parsers agree. So far they seem to :-)
You can read more about it here: https://gazagnaire.org/blog/2026-03-31-ocaml-wire.html.
To try it: opam install wire
Feedback very welcome on the issue tracker (or bellow)!
valkey: modern Valkey client for OCaml 5 + Eio, on opam
Archive: https://discuss.ocaml.org/t/ann-valkey-modern-valkey-client-for-ocaml-5-eio-on-opam/18010/1
Avi Fenesh announced
Hi everyone,
I wanted to share that valkey 0.2.0 is now on opam:
- opam: https://opam.ocaml.org/packages/valkey/valkey.0.2.0/
- Repo and Getting Started: https://github.com/avifenesh/ocaml-valkey/blob/main/docs/getting-started.md
The main thing I wanted with this project was to focus on the current Valkey stack with the newer OCaml stack.
So valkey is built around:
- OCaml 5
- Eio-native direct-style concurrency
- RESP3 only
- Modern Valkey features and real cluster behavior
A few parts I'm happy with:
- Cluster support with a strong focus on durability and high availability:
topology refresh, periodic background refresh,
MOVED/ASK/CLUSTERDOWNhandling, replica-aware reads, AZ-aware routing, and failover-aware sharded pub/sub replay - connection/runtime behavior:
circuit breaker, reconnect handling, keepalive, TLS, and optional separation between socket I/O and parsing via
Eio.Domain_manager, so one side of the system does not block the other - batch support:
scatter-gather across slots, atomic single-slot flows with
WATCH/MULTI/EXEC, multi-slot helpers, and in0.2.0also WATCH guards for read-modify-write CAS plus cross-slotpfcount_cluster - command surface:
typed helpers across a pretty broad set of commands,
Client.custom/~custom_multi~ for custom commands and multi-node execution, and named commands so you can register command templates once and reuse them later - scripting:
local script caching and optimistic
EVALSHAhandling with automatic fallback/retry onNOSCRIPT, so callers don't need to manage that flow themselves
There is also already a pretty good amount of validation around it: integration tests, property tests, fuzzing, chaos testing, examples, and guides.
Performance-wise, it also came out pretty nicely: in several scenarios it gets to 90%+ of the C reference client.
opam update opam install valkey eio_main
Current next steps on the roadmap are things like:
- client-side caching
- connection pools / blocking pools
IAM+mTLS- Valkey module support (JSON, search, bloom)
If there's a feature people care about, I'd be very happy to reprioritize the roadmap around real interest.
I'd also really love feedback from the OCaml community on the API, ergonomics, docs, and general design.
I'm very familiar with the Valkey world, but OCaml is more of a side fun for me than my daily language, so feedback from people who really live in the OCaml ecosystem is especially valuable to me.
If you try it, I'd love to hear what you think.
UnifiedScript_{Std,Top} - literate testing
jbeckford announced
I am pleased to introduce the UnifiedScript_Std and UnifiedScript_Top packages. Unified scripts are a way to write tests and documentation simultaneously. Cram tests and MDX scripts are both similar to unified scripts, but unified scripts aren't tied to Markdown (MDX) or tied to POSIX shell commands (cram tests).
Examples:
- Markdown example: Here are the zillions of tests from
ocaml-reconverted into Markdown: ocaml-re/EXAMPLES.md. Custom OCaml REPL printers are used so that many of the tests are printed as Markdown tables. aside: Wouldn't it be nice if tests you are already writing became documentation? That was a 4 year follow-up to https://discuss.ocaml.org/t/what-are-the-biggest-reasons-newcomers-give-up-on-ocaml/10958/13?u=jbeckford .mlexample: The following is a snippet from a regular OCaml.mlmodule where the(* ... *)comments are OCaml REPL toplevel responses maintained by the unified tools.let lyrics = "Everybody step to the left." (* val lyrics : string = "Everybody step to the left." *)[@ocamlformat "disable"] let (_ : string) = Printf.sprintf "Now let's sing: %s" lyrics (* - : string = "Now let's sing: Everybody step to the left." *)[@ocamlformat "disable"]
Docs are at dk/docs/UNIFIED_SCRIPTS.md (of course generated by the unified tools).
Install the tools with:
opam pin add UnifiedScript_Std https://gitlab.com/dkml/build-tools/MlFront/-/releases/permalink/latest/downloads/MlFront.tar.gz opam pin add UnifiedScript_Top https://gitlab.com/dkml/build-tools/MlFront/-/releases/permalink/latest/downloads/MlFront.tar.gz
After feedback I'll release them officially to opam. I would like feedback overall and also specifically on the agent skill that converts expect tests to unified tests.
openrouter_api: An OpenRouter client library
mt_caret announced
I'm happy to announce the first version (v0.0.1) of openrouter_api, a library for querying large language models via OpenRouter, a service which provides access to a variety of models under a unified API.
- opam: https://opam.ocaml.org/packages/openrouter_api/
- GitHub: https://github.com/mt-caret/openrouter-ocaml
- docs: https://mt-caret.github.io/openrouter-ocaml/openrouter_api/index.html
I've been using this library in my personal projects whenever I want to integrate an AI model, and I've found it quite useful; I hope others will find it useful as well. Issues and contributions are welcome.
Call for presentations: Caml in the Capital II
Continuing this thread, Sacha Ayoun announced
Date and location have been confirmed! June 3rd at the Jane Street office!! I have updated the post.
We're still open for presentation proposals as well!
Old CWN
If you happen to miss a CWN, you can send me a message and I'll mail it to you, or go take a look at the archive or the RSS feed of the archives.
If you also wish to receive it every week by mail, you may subscribe to the caml-list.