This file provides guidance to AI agents (Claude Code, Codex, etc) when working with the Dune codebase.
-
Every commit must be pass the following check
$ dune build @check @fmt @runtest. Commits that introduce a failing test should also pass this check even if the test introduces failure -
Unless otherwise stated, prefer to create commits for every logical change that you make. When in a
jjrepo, detected by the presence of.jj/, use thejjtool create commits. Commits should come with a brief description of the change. -
In a
jjrepo, orient yourself using$ jj showand$ jj log. Are you working off the right commit? Is it correct to modify the current commit? -
Before fixing a bug, demonstrate it first in a test. A fix should ideally flip the output of an existing test from failure to success.
-
Before writing large changes, try to estimate the scope of the change you intend to make. Attempt to understand how will the change interact with other features, what will be the testing strategy to make sure the test is correctly implemented.
-
Push back against user requirements before working on large changes. Ask for clarifying questions.
-
Avoid introducing callbacks, optional arguments, and general ways of indirection. Write code that is as direct as possible. If it can't be done, explain why not. Propose ways to re-arrange the code so that things can be done without indirection.
Most Common Commands:
dune build @check # Quick build (recommended for development)
dune runtest dir/ # Run tests in specific directory
dune fmt # Auto-format code (always run before committing)
dune promote # Accept test output changes (ask user first)
make dev # Full build (bootstraps automatically if needed)Special Operations (Ask User First):
make bootstrap # Rebuild entire toolchain from scratchNote: dune refers to ./dune.exe (the bootstrapped version).
Dune is a self-hosting OCaml build system that uses itself to build itself.
Key Concepts:
- Bootstrap: Building dune from scratch using
make bootstrap(ask user first) - Cram Tests:
.tfiles containing shell commands and expected outputs - Test Promotion: Accepting new test outputs when behavior changes
- Self-hosting: Dune builds itself using a previously built version
Directory Structure:
bench- performance benchmarksbin- dune's command line interfaceboot- bootstrap mechanism for building dune itselfdoc- user documentationdoc/dev- developer documentation (specs, design notes, review guidance)otherlibs- public libraries (dune-configurator, dune-build-info, etc.)src- the majority of the source codesrc/dune_rules- build rule generation (main logic)src/dune_engine- incremental build enginesrc/dune_lang- configuration file parsersrc/dune_pkg- package managementsrc/fiber- async/concurrency librarysrc/stdune- dune's standard librarysrc/dune_tui- terminal UI components
test- dune's test suite (.tfiles are cram tests)vendor- 3rd party code pulled into dune
Refer to doc/hacking.rst for granular instructions on developing Dune. In
particular, always write code according to the guidelines and style described
in the hacking document.
dune build @check # Quick build (recommended for development)
dune build @install # Full build
dune fmt # Auto-format code (always run before committing)What it is: Bootstrap solves Dune's circular dependency (Dune builds Dune)
using boot/bootstrap.ml - a mini-build system that creates _boot/dune.exe
without reading any dune files.
When needed:
- Fresh repository checkout (no
_boot/dune.exeexists) - Changes to core build system dependencies in
boot/libs.ml - After certain clean operations that remove
_boot/
Why ask user first: Bootstrap rebuilds the entire toolchain from scratch
using a carefully orchestrated process. Most development uses the existing
_boot/dune.exe.
When NOT to bootstrap: For normal development work, use dune build @check
or make dev. Bootstrap is only needed for the specific circumstances above.
Commands:
make bootstrap- Full bootstrap rebuild (ask user first)make test-bootstrap- Test bootstrap mechanismmake dev- Automatically bootstraps only if necessary
Bootstrap vs. built dune. _boot/dune.exe (behind ./dune.exe)
is not refreshed when source files change. For experiments that
exercise dune's rule generation or build-system behavior, use the
fully-built dune at _build/install/default/bin/dune (run make dev
first). Cram tests already use the built dune; manual reproductions
driven by ./dune.exe can diverge silently from the source being
investigated.
When a test result disagrees with a manual reproduction — or CI disagrees with local — verify both are the same binary built from the same source:
- Which
duneis on PATH or called explicitly? - Is its timestamp newer than the source files being analyzed?
- Does its build context match the source tree being analyzed?
The bootstrap-vs-built mix-up is the commonest trap; checking up front avoids hours of reasoning about non-existent bugs.
dune runtest dir/ # Run tests in specific directory
dune runtest dir/test.t # Run specific .t test (cram test)
dune runtest # Run all tests (567+ tests, very slow)Output Handling: Dune is generally silent when building and only outputs
errors. Avoid truncating output from dune build and dune runtest. If
dune runtest gives too much output, run something of smaller scope instead.
Test Promotion: When tests fail due to output changes, ask user before
running dune promote to accept changes.
Experimentation: Create cram tests (.t files) to experiment with how
things work. Don't run commands manually - run them through dune runtest to
capture and verify behavior.
Printf Debugging: When confused about behavior, use Console
(commonly aliased as Console) for debugging:
Console.printf "something: %s" (Something.to_dyn something |> Dyn.to_string);This output will appear in cram test diffs, making it easy to observe values.
Trace Inspection: Use dune trace cat | jq to inspect build traces. See
doc/hacking.rst for details on using jq with traces in cram tests.
- Always verify changes build with
dune build @check - Run
dune fmtto ensure code formatting (requires ocamlformat) - Keep lines under 80 characters
- Only add comments for complex algorithms or when explicitly requested
- Don't disable warnings or tests unless prompted
- Use pattern-matching and functional programming idioms
- Avoid
assert falseand other unreachable code
- Every
.mlfile needs corresponding.mli(except type-only files) - Use
Code_error.raiseinstead ofassert falsefor better error messages - Qualify record construction:
{ Module.field = value } - Prefer destructuring over projection:
let { Module.field; _ } = recordnotrecord.Module.field - Do not write
to_dynfunctions. WriteRepr.tvalues and use those to constructto_dyn.
NEVER do these things:
- NEVER create files unless absolutely necessary
- NEVER proactively create documentation files (*.md) or README files
- NEVER stage or commit changes unless explicitly requested
- NEVER run
dune clean - NEVER use the
--forceargument - NEVER try to build dune manually to run a test
- NEVER run dune in parallel
- NEVER delete the dune lock file
ALWAYS do these things:
- ALWAYS prefer editing existing files over creating new ones
- ALWAYS ask user before running
dune promoteormake bootstrap