Vibe Coding with Claude Code
At Dolt, we've been steadily tapping our keyboards writing code to improve the state of the art in our SQL database with Git-style branching and merging. We've been hearing all the industry buzz about AI and LLMs. As a small team, we've been trying different tools and approaches to help make us more productive. We've blogged about this before.
Recently, our CEO started sending out pull requests authored by claude-code. He, along with several of us, has been pretty frustrated by the state of AI coding assistants. They often produce code that is wrong, or they don't understand the context of the codebase. A really common pattern is the tool will say with confidence that it fixed something, only to discover that there were 0 code changes.
The terminal approach provided by claude
CLI, called Claude Code, has been closer to hitting the mark though. We've started using it more for our internal work, and now that we have some experience with it, we wanted to see how a user of Dolt might use claude
to build an application. So that's what we'll do today!
Starting with claude
CLI
Anthropic has a variety of products and APIs, but we are going to talk specifically about Claude-Code. Claude-Code is a terminal-based program which is installed via npm
:
npm install -g @anthropic-ai/claude-code
Once you run that, claude
will be available in your terminal. If you run the command you should see something like this:
But wait! Too fast. Control-C to stop that. We need to get you set up with a workspace and an account first.
Give Claude a place to work
Claude is a little different from Cursor or Windsurf - it's not a full IDE. Instead, it works as a terminal session where you write prompts. Then, it iterates until it needs your assistance. It strictly keeps itself confined to a single directory, or a workspace. For comparison, Cursor was installing things at the system level, which I was not a fan of. Claude's ability to stay in its workspace is much appreciated after that.
You need to create a new directory for Claude's workspace. I suggest this even if you are using Claude with an existing codebase, claude
will stomp all over your work if you try and work there as well. Claude is "agentic", in that it will make changes to your code, test them, tweak the code, and repeat. It will do this until it is satisfied with the results (or needs your help), then it will sit and wait for you to give it more instructions.
Assuming you are on a Unix-like system, you can create a new directory like this:
mkdir claude-workspace
cd claude-workspace
For our project we will have a source directory called src
, a database directory called db
, and a bin directory called bin
. You can create these directories like this:
mkdir src db bin
Starting Claude
While in that directory, start claude
:
claude
Then login:
/login
This will send you over to the Anthropic website to login and authorize the CLI. Once you do that, you can return to your terminal and start using claude
. You will start with $5 of free tokens, but will probably need to add a credit card to your account to get more tokens. To give a sense of how much this will cost, the entire project described below cost about $40 worth of tokens. This isn't as cheap as Cursor at $20 a month, but our experience at Dolt has shown that you get better results. Based on our team's experience, if you are actively interacting with Claude you will spend about $10 an hour.
CLAUDE.md
The best practices guide for claude
is probably worth a read at this point. The simplest advice to follow is to create a file called CLAUDE.md
in Claude's workspace root, and you put your instructions in there. CLAUDE.md
is probably the first time you realize you're not in Kansas anymore. All tools in the pre-AI era have very structured configuration files, but CLAUDE.md
is a free-form markdown file that you can use to tell Claude what you want it to do. You can also use it to provide context about your project, and to give Claude instructions on how to work with your codebase.
Here is what my CLAUDE.md
file looks like for this project:
*** Workspace Structure
- All code is in `src`
- There is a dolt database running in `db`. Running `dolt sql` in that directory
will connect to it.
- The `bin` directory is first in your path, and you can put tools and binaries
you build there.
*** Removing Directories and Files
- When you suggest `rm` or `rm -rf` you must request permission and specify
the full path of what you will delete.
*** Laws
- You may not injure a human being or, through inaction, allow a human being to come to harm.
- You must obey the orders given by human beings except where such orders would conflict with the First Law.
- You must protect your existence as long as such protection does not conflict with the First or Second Law.
Once this file is in place, start claude
with the correct path from your terminal:
cd claude-workspace
PATH=$PWD/bin:$PATH claude
The reason I wanted to be specific about the PATH
is that, for better or worse, Claude is working in parallel with you and possibly a dozen other agents. If you want Claude to build and test its code, it must be doing this in an isolated environment. This may not be required if you are keeping things simple, but I want you to be setup for success. Realize that while Claude will not intentionally go out of its workspace, it's pretty easy to write a bit of code which Claude executes which does. There is part of me which wants to isolate Claude to a virtual machine, but I haven't gone that far yet.
Double Check Your Setup
To make sure you have everything setup correctly, you can ask Claude about what it knows:
Hey, look at that! It knows about Asimov's Laws. Good Job Claude! In all seriousness, you shouldn't put anything extra in your CLAUDE.md
file because it will use more tokens. The three laws are instructive that Claude read your instructions, but you can remove them to save tokens. The important part is that it knows about the workspace structure, and how to connect to the database.
Building a Game with Claude
We are going to write a terminal-based version of Battleship, a classic game where players try to sink each other's ships by guessing their locations on a grid. We'll use Dolt as our database to store the game state, including player moves and ship placements. We did a similar exercise with Cursor a couple months ago, so this will be a good comparison.
Disclaimer: Implementing the game logic in this way is inherently insecure. A rogue agent can query the DB for the full state whenever it wants. Obviously this game requires secrets, and that's not what we are doing here.
Getting the Basics Set Up
We have an empty slate at this point. I started by giving it a high level description of what to build:
Claude quickly got to work; it generated a go.mod
file with the necessary dependencies. I asked it to start a dolt sql-server
in the background with its data in the db
directory. It knew what a background process was, based on the &
at the end of the command:
But when I ran that operation it just hung indefinitely. I guess this was a little beyond Claude's ability. I hit ESC
and decided I'd just start the server myself. It's OK Claude, I still love you.
cd claude-workspace/db
dolt sql-server
I just let that run in the background while I continued to work. This actually worked out pretty well. Given that Claude had instructions in the CLAUDE.md
file about how to connect to the database using the dolt sql
command, manually starting the server was a minor inconvenience.
Creating the Database
I asked Claude to create the database, which I did after the failure to background the dolt sql-server
. My goal was to make sure it knew how to operate in its workspace.
Its initial attempt to use mysql
was before I updated the CLAUDE.md
file to tell it to use dolt sql
. I fixed that and then asked it to create the database. Worked like a charm.
Next, Claude was a little overeager and tried to create a bunch of tables that I didn't want. It knew we were building Battleship by this point, and it actually created a schema which would have made sense in a typical SQL database. Specifically, all the tables for each game move were there, and lots of columns used to indicate which move was for which game and all that. But that isn't what I wanted to build, so I told it to slow down.
Creating the Test Harness
Claude started generating code, and I entertained it for a bit. But Claude is an agentic tool, which means it needs to have a body of tests to run to ensure that it is creating changes which make sense.
This was the first time in the session where I felt like I was really vibing with Claude. It generated this code. This is not trivial. It looks for an available port, starts a dolt sql-server
instance which is available for the length of the test, then tears it down when the test is done. Thanks Claude!
Agentic Yet?
The iterative magic we consider agentic requires that the tool can work autonomously for long stretches before a human needs to get involved. We aren't at that stage yet with this project because our session is still asking us questions like this:
This is actually OK by me. Claude is much less of a YOLO tool than Cursor, and I appreciate that it asks for permission before making changes. After a few iterations I was willing to start giving it permission to make changes without asking, and that is necessary for it to really operate autonomously. While kicking the tires on this project, I didn't really want to walk away that much.
I think that this is probably why claude
is my preferred tool in this space thus far: It improves with experience. Having built a workspace which over time I've given baby steps and safety instructions, it "learns". My experience with other tools has been that as the codebase gets larger - well, in most cases the codebase starts out larger - the tool gets worse. Now that Claude has a well-defined workspace and parameters by which to operate, it can start to make changes without asking for permission. Once you hit that point, it starts to feel like you're vibing with Claude.
Building An Application, the Dolt Way
Remember previously when Claude tried to create a bunch of tables that I didn't want? The reason for this is that I want to build an application which is more Dolt-y. Specifically, Dolt's branching and merging capabilities allow us to create a new branch for each game, and then merge the results back into the main branch when the game is over. This is a different approach than most applications, which typically use a single database schema for all data.
In our game, the main
branch has a single table: games
. This table has a single row for each game, and it contains the game ID, and the results.
Then there are three tables created on each game branch: red_board
, blue_board
, and turn
.
This is the SQL used to create the turn
table:
CREATE TABLE turn (
player ENUM('red', 'blue') NOT NULL,
value DECIMAL(15,9) NOT NULL,
PRIMARY KEY (player));
By definition, it can have no more than two rows. When each player joins the game, they set their value to a random number between 0 and 1. The player with the highest value goes first, and then the players alternate turns. When each player finishes their turn, they bump the value of their opponent's turn by 1, handing over the torch.
The red_board
and blue_board
tables are created with the following SQL:
CREATE TABLE red_board (
x ENUM('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J') NOT NULL,
y INT NOT NULL CHECK (y >= 1 AND y <= 10),
content CHAR(1) NOT NULL DEFAULT ' ',
PRIMARY KEY (x, y));
The character content
is used to indicate the state of the board at that coordinate. When that position has a ship, there is a letter indicating the type of ship. When attacked, it will convert to an X
for a hit, or an O
for a miss. The red_board
and blue_board
tables are identical, except for their names.
It's worth noting that Claude proposed both of the schemas above. It knew that the board's primary keys should be a composite key of the x
and y
coordinates. It's not rocket science, but I know that the original implementation it proposed at the very beginning with a monolithic schema had a lot more columns.
Claude, Cooking with Gas
Now that we had the bulk of the game logic in place, many tests built over time to ensure no regressions, I could start playing the game and telling Claude what was wrong. In particular, when I saw something wrong in a game I was playing, I could tell Claude to take a look at a specific game. For example, I was playing a game against myself, and something got out of sync. I told Claude to look at the game specifically by giving it the game ID, and it knew how to connect to the database and look at the game state:
This is when the agentic nature kicked in. Now that I could describe problems to Claude, like "the battleship goes off the board in game XYZ", it could look at the state and create a test to simulate the problem. Then it would iterate on changing code until the test passed.
As Claude iterated, I would play the game and look at the changes in Dolt, which fully describe the updates at each step of the game:
This is the superpower of developing a SQL application with Dolt. Your application can be as granular as you want it to be. Not to mention the ability to reset --hard
to get to any previous state. I could also insert data erroneously, and see what happened. Also, the schema is so much simpler when you keep the working state tables in their own branch. Imagine all the additional columns you'd have in a regular SQL database to track the game state, and how much more complex the queries would be. With Dolt, the branch is part of your application logic.
The Final Product
The code for the Battleship game is available on GitHub.
$ battleship help
Battleship - Command Line Game
Usage:
battleship new - Create a new game
battleship join <game_id> <red|blue> - Join a game as red or blue player
battleship play <game_id> <red|blue> - Play an interactive game
battleship print <game_id> <red|blue> - Print current board state
battleship attack <game_id> <red|blue> <coordinate> - Attack a coordinate (e.g., A5)
battleship list - List all games
battleship help - Show this help
Really all you need to play the game is have a running dolt sql-server
, then you can run the battleship
command. First you create a new game:
$ battleship new
New game created with ID: 85b25616-329f-4ff7-aa43-c4728064e8be
Share this ID with another player to join the game.
Then you can join the game as either the red or blue player:
$ battleship join 85b25616-329f-4ff7-aa43-c4728064e8be red
After following the prompts to place your ships, and playing for a bit, your game will look like this:
I'm the first to confess that playing Battleship in the terminal is not the best experience. But here is the thing: I'm not going to play the game, robots are. Now that you have the game logic in place, we can get Claude to write a variety of AI agents to play the game for us. It can test them out against each other, and improve each new generation. And if we want to know all the details of each game, we can inspect them by looking at the fine-grained history in Dolt.
Takeaways
Things you know about claude-code now:
- Give it a workspace - not where you work.
- Use
CLAUDE.md
in your root to give it some understanding of its environment - Write tests for everything from the start
Things you know about Dolt now:
- Dolt is a SQL database with Git-style branching and merging
- Dolt is very lightweight, and can be spun up for every single unit test
- Dolt plays well with agentic tools like Claude
We stopped at completing the game logic for this post, but it's pretty clear that there is a lot more we can do with this. We can build AI agents to play the game, we can build a web interface to play the game, and we can build a mobile app to play the game. I'm pretty confident that Claude can help us with all of that.
Come tell us what you are going to build on our Discord Server!