PRODUCTS

KEYWORDS

DoltLite Code Review Highlights

Exactly two weeks ago, our CEO Tim announced DoltLite on our blog. DoltLite is a port of Dolt’s fundamental data structures, algorithms, and resulting features to SQLite. Dolt is a version-controlled SQL database that brings Git-like version controlled semantics to SQL, allowing you to branch, merge, diff, clone, push and pull your SQL database. DoltLite brings all of those features to a forked version of the SQLite code base.

DoltLite Logo

Up until now, DoltLite has been purely vibe-coded. No human has ever meaningfully tried to read or understand what the agents were up to as they built it. I was dying to know what was underneath the hood, Tim is my boss so I spent a couple of days reviewing DoltLite and getting a better understanding of how it works. This is a short overview of some of my findings.

Background#

As described in the initial announcement blog post, DoltLite was purely vibe-coded by our CEO as his first foray into large scale agentic coding. We were all surprised at how fast GasTown, driven by Tim, was able to get a credible prototype up of Dolt’s versioning capabilities in C and with SQLite as the SQL engine.

Since then, Tim has continued to iterate on DoltLite, adding features and improving benchmark performance. Overall, Claude has landed 465 commits in the DoltLite repository’s main branch since the project started a little over 3 weeks ago, on March 16th, 2026.

All of this code was written without Tim actually reading any of it. He prompted by suggesting approaches that would be necessary to overcome certain obstacles, based on his experience running DoltHub and building Dolt over the last eight years, but he did not concern himself with the code at all. He did have Claude periodically code review itself and he asked it to clean up dead code and remove redundancy. He observes that eventually it required a lot of rhetorical techniques — cajoling, berating, pleading — to get Claude to refactor or question previously made assumptions when it needed to get over particular roadblock.

Tim attributes a lot of the success of the DoltLite prototype experiment to the fact that SQLite has a bunch of tests and other SQL test harnesses exist as well. Agents need tests to stay on track and to keep them honest. So DoltLite passes the tests that exist. That’s basically been the only bar to which it has been held throughout its development. It also, somewhat concerningly, wrote many of those tests, particularly the ones for DoltLite-specific functionality. The entire team is wondering — what does the code actually look like and how maintainable would DoltLite be if we tried to turn it into an offering from DoltHub?

A Quick Caveat#

As a disclaimer, I’m far from an expert in C, which has never been a daily driver language for me in my 20 years as a software engineer. I’m also completely new to the SQLite code base internals, although I have used and am familiar with its public API.

An Overview#

When you jump into the DoltLite code base and start comparing it to the original SQLite source code, you find it includes a few new subcomponents:

Subsystem~ Number of LinesPurpose
prolly_btree.c5,600Implementation of SQLite’s struct Btree and related APIs (struct BtShared, struct BtCursor, etc.)
prolly_*.{h,c}5,300Support datastructures and algorithms for prolly_btree — nodes, cursors, hashsets, chunker, diff and three-way diff
chunk_store2,400Content-addressed storage of chunks — the file format and file mutation routines
doltlite_*.{h,c}9,200Git-like operations against the DoltLite database — commit, branch, checkout, diff, log, etc.
Other2,500Various changes and shims necessary to stitch everything together

Overall, we are dealing with approximately 23,000 lines of code for the new functionality.

The style of the new code seems to broadly follow the existing sqlite3 conventions. Braces, parentheses and block syntax are generally pretty consistent with existing practice in the code base. It mostly sticks to C89, although that was not enforced by any tests or lints during development and C99-isms appear in various places. The code generally uses sqlite3-specific types and wrappers — things like u{8,16,32,64}, and sqlite3_{malloc,free}.

Some Initial Nits#

Diving in just a bit deeper, I came across some duplication and messiness that left something to be desired.

Claude decided to use little-endian for integer encoding in various hand-rolled serializations it cooked up, despite the fact that SQLite uses big-endian. Perhaps it was influenced by Dolt, which does use little-endian (often by way of flatbuffers). Whatever the reason, I’m not too concerned about that actual choice, but more by the fact that multiple copies of get and put u32 macros exist, as well as macros for get and put i64 and u16 being separated out into different subsystems. More heavily used than any of those macros are just inline duplicated code all over the place that quickly decodes or encodes a 32-bit uint. SQLite itself is not entirely dogmatic about using helpers like sqlite3Get4byte but I would have preferred to see more discipline here.

In a similar vein, especially in the doltlite_ layer, there were lots of duplicated function declarations and struct definitions that properly belonged in a header file. A struct TableEntry data structure was defined the same way across 10 different .c files. A function, BtShared *doltliteGetBtShared(sqlite3 *db), was declared extern in 6 different doltlite_*.c files. At some of those sites it was declared as returning void * instead.

Not to belabor the point, but several doltlite_ files were reading down into the BtShared layout by doing offset-based pointer arithmetic to extract one of its members. Something like (ProllyCache*)(((char*)pBt) + sizeof(ChunkStore))

These later nits mostly apply to the doltlite_ layer. And I think, from my reading, that Tim probably had GasTown go wider on concurrent work when working on those features. He certainly also had Claude implement these features after the prolly_ layer and he might have spent less time instructing Claude to review and clean up its own sloppiness here.

Diving Deeper into Persistence#

I wanted to get an understanding of how persistence and the commit protocol worked so I headed over to read through the chunk_store. One thing that stood out quickly was that the code seemed to have been through multiple interations without sufficient cleanup. There were dead and unused fields on the core data structures, making it a bit difficult to tease out where certain information was stored and how all the pieces fit together. There were also numerous stale comments, sometimes referring to code which simply didn’t exist and other times justifying or describing why something was the way it was by making reference to behavior which did not exist.

I came across three major concerns involving correctness in my short and somewhat superficial read.

Fsync Is Subtle And Overwrites Are Scary#

First, a particular combination of a stale comment and a critical durability bug caught my eye. I knew from my previous reading, my work on Dolt crash recovery testing, and from working on the persistence layer of Dolt itself for the last 6 years that durability is hard to come by without extreme paranoia. So the following, towards the end of the code which writes and commits new data to chunk_store, stood right out:

  /* Write root record: tag(1) + manifest(168) */
  {
    u8 rootRec[1 + CHUNK_MANIFEST_SIZE];
    rootRec[0] = CS_WAL_TAG_ROOT;
    csSerializeManifest(cs, rootRec + 1);
    rc = sqlite3OsWrite(cs->pFile, rootRec, sizeof(rootRec), writeOff);
    if( rc != SQLITE_OK ) goto commit_done;
    writeOff += sizeof(rootRec);
  }

  /* Also update manifest at offset 0 for fast open (skip WAL replay) */
  {
    u8 manifest[CHUNK_MANIFEST_SIZE];
    csSerializeManifest(cs, manifest);
    rc = sqlite3OsWrite(cs->pFile, manifest, CHUNK_MANIFEST_SIZE, 0);
    if( rc != SQLITE_OK ) goto commit_done;
  }

  /* fsync via VFS — durability point */
  rc = sqlite3OsSync(cs->pFile, SQLITE_SYNC_NORMAL);
  if( rc != SQLITE_OK ) goto commit_done;

This is part of the commit protocol that chunk_store implements. chunk_store is responsible for implementing a content-addressed key-value store for chunks, which are the fundamental unit of storage in Dolt. For example, Prolly Trees are expressed in terms of chunks, encoding the content addresses of their children within their contents. The chunk_store which was implemented in DoltLite has two parts. The first part is an immutable set of chunks. It appears at the beginning of the file and includes as its suffix an index which, when loaded, allows for finding a chunk’s offset in the file by its address. The second part is an append-only log of two different types of records: chunk records and root chunk update records. The root chunk record is just a record that points to the “root” value of the entire version-controlled database, including all its branches.

The chunk_store is designed so that at offset 0, before the immutable set of chunks and its index, a single root chunk record is present. In theory, that root chunk record should point to the root chunk as it existed when the immutable set of chunks was built. Instead, this code tries to update it on every commit to the chunk store. This is unnecessary, because there is no meaningful way to skip “WAL replay” and no such code path exists, despite what the proximate comment says. Even calling the second half of the chunk_store file a WAL is misleading — it’s actually an append-only log that is the system of record for what is in the store.

In addition to being unnecessary, it’s also unfortunately dangerous. There’s a critical durability bug in writing a reference to the data at offset 0, overwriting the previously available reference, before the actual data itself has been fsync’d to the file. If the operating system flushes the sector/block at offset 0, and then crashes, there is no guarantee that the other data which that write depended on, and which was appended to the end of the file, actually also made it to disk. But maybe you won’t notice when you try to open the database, because…

Ignoring Errors Is Ill-advised#

That wasn’t the only durability red flag that stood out. The code as written, as far as I can tell, is happy to ignore when certain very important pieces of data are simply missing or cannot be parsed. For example, in chunkStoreOpen, which is the code path which initially loads the chunkStore and gets it ready for use, the following appears:

    /* Load refs chunk (may be in compacted or WAL region) */
    if( !prollyHashIsEmpty(&cs->refsHash) ){
      u8 *refsData = 0; int nRefsData = 0;
      rc = chunkStoreGet(cs, &cs->refsHash, &refsData, &nRefsData);
      if( rc==SQLITE_OK ){
        csDeserializeRefs(cs, refsData, nRefsData);
        sqlite3_free(refsData);
      }
    }

The code drops at least two separate error cases on the floor. First, chunkStoreGet can return an error, including SQLITE_NOTFOUND. If we can’t find the referenced chunk, we should fail to open the store. Similarly, csDeserializeRefs can fail to successfully deserialize the chunk we did find as actual refs data. The return code of csDeserializeRefs isn’t checked at all. Failing to load the refsHash for a database means that all data in the database has been lost. The refsHash was the entry point to every branch, every tag, and every remote in the entire database. Silently ignoring whatever is happening here and continuing on, instead of returning SQLITE_CORRUPT, seems really bad. The same pattern of incorrect error handling on loading refsHash is repeated in a number of other places where the chunk_store is read, including chunkStoreRefreshIfChanged and csReplayWalRegion.

There’s Something Missing Here…#

Finally, in all the code I read for the commit and the reload of the journal, I didn’t see any code handling an application-level merge of the changes which the caller was landing with those which had been landed concurrently from the caller as it was building its own changes. DoltLite’s README said:

SQLite’s Btree struct is per-connection. Doltlite stores each session’s branch name, HEAD commit hash, and staged catalog hash there. The underlying chunk store (BtShared) is shared. This means:

  • Two connections to the same database file can be on different branches.
  • Each sees its own tables, schema, and data based on its branch’s HEAD.
  • dolt_checkout reloads the table registry from the target branch’s catalog.
  • Writers are serialized by SQLite’s existing locking, so branch switches are safe.

I didn’t see any of the code I would expect to see for most of those claims to be true. It seemed like if one connection made a change to a branch, and a different connection made a concurrent and different change to the same branch, or even a different branch, the first change could easily be lost.

In local testing, I was able to verify this without too much trouble. So, for example, in a first terminal:

$ ./doltlite file.db
doltlite> create table vals (id int, val int);
doltlite> select dolt_commit('-A', '-m', 'Initial commit');
╭──────────────────────────────────────────╮
           dolt_commit('-A',...
╞══════════════════════════════════════════╡
 ecb12234b9ca5354ee05e083906c5ed95af9f543
╰──────────────────────────────────────────╯

In another one:

$ ./doltlite file.db
doltlite> select * from vals;

Back to the first:

doltlite> insert into vals values (1, 1);
doltlite> select dolt_commit('-A', '-m', 'add one');
╭──────────────────────────────────────────╮
           dolt_commit('-A',...
╞══════════════════════════════════════════╡
 390b5455dc88644a533bd6639a6fe8146e045bf8
╰──────────────────────────────────────────╯

And the second:

doltlite> insert into vals values (2,2);
doltlite> select dolt_commit('-A', '-m', 'add two');
╭──────────────────────────────────────────╮
           dolt_commit('-A',...
╞══════════════════════════════════════════╡
 66d6ba795a71dbddb12f196b1924fec6fb9ae115
╰──────────────────────────────────────────╯
doltlite> select * from vals;
Error near line 4: out of memory

And in the first:

doltlite> select * from dolt_log;
╭──────────────────────────────────────────┬───────────┬───────┬─────────────────────┬────────────────╮
               commit_hash committer email        date    message
╞══════════════════════════════════════════╪═══════════╪═══════╪═════════════════════╪════════════════╡
 66d6ba795a71dbddb12f196b1924fec6fb9ae115 doltlite 2026-04-07 20:43:37 add two
 ecb12234b9ca5354ee05e083906c5ed95af9f543 doltlite 2026-04-07 20:42:43 Initial commit
╰──────────────────────────────────────────┴───────────┴───────┴─────────────────────┴────────────────╯

What happened to 390b5455dc886...?! Obviously the commit protocol failed to account for it, although in this case and for this particular sequencing, both pieces of actual data did manage to make it to final tree. I found it easy to come up with other sequences, especially involving transactions, where data just completely disappears even after a select dolt_commit.

I came to the unfortunate conclusion:

DoltLite does not correctly implement multiple connections

Ideally it would at least implement exclusive access to the underlying database file, so that silent corruption didn’t happen unexpectedly for clients. It doesn’t manage to do that either.

Broader Impressions#

More broadly, after reading through the code for a bit, two impressions formed pretty strongly:

  1. In order to clean things up appropriately, there’s a strong need for layering and interfaces. This would be most effective if implemented in policy that the LLM itself can’t easily challenge or change. For example, SQLite is all in the src/ directory and nothing structurally prevents certain components from depending on others. But Claude would benefit from being more constrained — the prolly_btree should depend on chunk_store, but not the other way around. Nothing in prolly_btree should depend on doltlite_, and certainly not on the public client-facing database functionality of SQLite like sqlite3_prepare_v2 and sqlite3_step. But those kinds of backwards links and layering violations exist in many places throughout the code.

  2. Based on my current understanding, the approach that Claude has found for implementing Dolt-like features in a SQLite fork is still somewhat unproven. There is no doubt that it has gotten impressively far into building a prototype — much further and much more quickly than I would have if I set about trying to hash out a completely rational integration that I was certain would work. And it’s possible that what it has settled on is actually credible for taking further. It’s possible that it works even today for the single-connection use case to be useful for some use cases. But I didn’t come away from my review of the code with a feeling that the settled architecture deeply understood and appropriately considered the integration points, the constraints, and the product surface area. It did not leave me with confidence that this approach can actually be iterated towards something that will continue to meet the intersection of those factors in the future. But maybe it can be. If it can be, I believe doing so will involve either one or both of (1) more human interaction and (2) a lot more tests and enforced constraints.

Final Thoughts#

In general I enjoyed reading through the code and seeing how SQLite worked and how Claude came to approach the integration. There are some clever pieces where things come together and Claude was able to finangle a Dolt-like model into an underlying engine which was in no way anticipating it.

I also had a number of unfamiliar feelings and thoughts as I reviewed the code. If a human colleague were to spit out 20,000+ lines of C and send it to me for review, and if it were filled with the same kind of detritus, the evidence of abandoned attempts, and the same clear lack of understanding, at parts, of what problems we were trying to solve or what was important about our attempt to solve them — well, that would feel bad. Instead, for most of the concrete things that came up as I was reviewing I immediately had the instinctive thought “Claude could fix this quickly if I asked him to.” And there’s also the reworked cost calculus, where this is a really neat prototype that I never anticipated being able to build so cheaply. It’s fun to see in action.

Overall I’m curious and excited to see what’s next for DoltLite and for agentic engineering in general. If you’re curious about Dolt or DoltLite, or if you’re reviewing the DoltLite code yourself and you find something cool that I missed, don’t hesitate to drop by our discord and let me know.