Repo & Version Control
An agent can edit dozens of files while you read one sentence. Git is what makes letting it do that a reasonable decision.
put a project under git, commit at the right moments, and undo anything an agent does to it
A coding agent edits faster than you can read. The question was never whether it will get something wrong. The question is what being wrong costs you — and with git the answer is one command.
Without version control, every agent run is a one-way door. You end up arguing with a machine about what it changed three prompts ago and slowly losing the version that worked. With version control, a bad run is a shrug: throw it away, write a better prompt, run again. Everything bold you are about to do — long runs, risky refactors, letting an agent near the database layer — is only sensible because you can get back.
Five words carry the whole system
| Word | What it is | Picture it as |
|---|---|---|
| Repo | Your project folder plus its full history, stored in a hidden .git folder. Works offline, needs no account. |
A binder that contains every previous version of every page |
| Commit | One saved snapshot: the exact state of the files plus a message explaining the change. | A diary entry with a photo attached |
| Branch | A parallel line of work. Build on it, and main stays untouched until you merge. |
Photocopying the master document to scribble on |
| Remote | A copy of the repo on a server — usually GitHub — that you push to and pull from. | The safe deposit box, off-site |
| Pull request | A proposal to merge one branch into another. Shows exactly which lines changed and runs checks first. | Handing a draft to an editor before it goes to print |
That is the vocabulary. Five commands cover most days.
cd qairu-signup
git init
git status
git add .
git commit -m "chore: first working version"
git log --onelinegit status is the one you will run most. It answers “what has changed since the last snapshot, and what is about to go into the next one”. git add stages the files that go into that snapshot, git commit takes it, and git log --oneline reads the history back one line per commit.
From a folder on your laptop to an address on the internet
Create an empty repository on GitHub (no README, no licence — you already have files), copy the URL it shows you, then connect the two.
git remote add origin https://github.com/YOUR-NAME/qairu-signup.git
git branch -M main
git push -u origin mainAfter the first push, git push on its own is enough. The other direction is git pull: it brings down commits the remote has and you do not — work you did on another laptop, or a change a teammate merged while you slept. Run it before you start, not after you have already edited the same file. Now your history exists in two places, and later modules can point a host at that URL and deploy on every push.
.gitignore is the list of things that must never leave your laptop
A .gitignore file tells git which paths to pretend it cannot see. Four lines cover a typical project:
.env
node_modules/
dist/
.claude/worktrees/.env holds your API keys — keeping secrets out of the repository is the entire point of environment variables. node_modules/ is reinstallable and enormous, dist/ is generated, and .claude/worktrees/ is where Claude Code puts parallel working copies once you run two sessions at once.
Write commit messages you can still read at 2 a.m.
A commit message has one job: let a future reader — you, a teammate, or an agent in a fresh session — decide in two seconds whether this is the commit they want to undo. The conventional-commits style makes that automatic: a type, an optional scope, one line of plain description.
feat(signup): store reservations in D1
fix(form): reject an empty email instead of saving it
test(form): cover the duplicate-email case
refactor(api): split the handler into two files
docs(readme): how to run the dev server
chore(deps): bump astroSix types carry almost everything: feat, fix, docs, refactor, test, chore. Keep the first line short enough to scan in a list, and put the why in the body if it needs one. The payoff is that git log --oneline becomes a changelog you can revert line by line. Anthropic’s write-up on harnesses for long-running agents arrives at the same habit from the agent’s side: have it commit after each unit of progress with a message that describes the change, so a later session can find its way back to a state that worked.
Undo has three shapes, and only one is safe after a push
This is the part to actually memorise. Match the situation to the command.
| What happened | What you run |
|---|---|
| The agent changed files, nothing is committed, you want the old state | git restore . for tracked files |
| It also created new files you do not want | git clean -fd — deletes untracked files and folders |
| You want to park the mess without losing it | git stash, then git stash pop to bring it back |
| The last commit is bad and you have not pushed | git reset --hard HEAD~1 — the commit is deleted |
| The bad commit is already pushed or shared | git revert HEAD — a new commit undoes it |
git clean -fd deletes untracked files permanently — git never recorded them, so nothing can be restored. Ignored files survive unless you add -x, so your .env is safe. Run git clean -nd first: -n lists what would go without touching anything.
Rewinds the branch and deletes the commit. Perfect while the work is still only on your laptop. After a push it is a trap: your history and the remote’s now disagree, the next push demands --force, and forcing over a branch others have pulled is how teams lose work.
Adds a new commit that is the exact inverse of the bad one. Nothing is destroyed or rewritten, and the log honestly shows both the mistake and its undo. Safe before a push and after it. This is your default.
Revert works on any commit, not only the last: read git log --oneline, copy the short hash, run git revert a1b2c3d. If later work built on it, git stops and shows you the conflict instead of guessing.
Checkpoints are local undo. Git is permanent undo.
Claude Code keeps its own safety net. Every prompt that starts a turn creates a checkpoint, and files are snapshotted before each edit; Esc Esc on an empty input, or /rewind, opens a menu that restores code and conversation, or just one of them. For “that last edit was wrong” it beats any git command.
It is not a replacement for git, and the checkpointing docs say so. Checkpoints keep only the 100 most recent per session and are swept about 30 days after the session last saved one. More importantly, they do not track changes made by Bash commands such as rm, mv and cp, most subagent edits, edits made outside the session, or symlinked and hard-linked files.
Commit before you press Enter
Here is the rule to leave with: git status should say “working tree clean” before you send a prompt that will touch more than one file. Then commit each unit of work that passes its own check, before starting the next one.
Two reasons, and neither is bookkeeping.
First: approval fatigue is measured, not theoretical. When almost every prompt gets a yes, clicking “allow” has stopped being a decision. And the classifier Claude Code’s auto mode uses to approve on your behalf still lets 17% of genuinely over-eager actions through. Neither layer is a wall. A commit before the run is the one guardrail that never gets tired.
Second: small commits make supervision cheap. A forty-line diff you will genuinely read. A two-thousand-line diff you will scroll and approve, which is not review — it is a signature.
Git rules for this repo:
- Before starting a task, run `git status`. If the tree is not clean, stop and tell me.
- Work on a branch named feat/<short-name> or fix/<short-name>. Never commit directly to main.
- Commit after each unit of work that passes its check. One logical change per commit.
- Message format: type(scope): what changed.
Types: feat, fix, docs, refactor, test, chore.
- Never run `git push --force`. Never amend a commit that is already pushed.
Never add .env or anything listed in .gitignore.
- When the task is done, show me `git log --oneline` for this branch and `git diff --stat`
against main, then wait for my review before opening a pull request.One branch per task, one pull request per review
Branch before the agent starts, not after: git checkout -b feat/seat-counter. If the run goes badly you delete the branch and main never knew. If it goes well, open a pull request — yes, even alone, even on a project nobody else will see. That is where you read the diff file by file, where automated checks run, and where a fresh agent session can review code it did not write.
Simon Willison’s catalogue of agentic anti-patterns names the one that matters here: do not open a pull request containing code you have not reviewed yourself; keep them small and include the evidence that it works. Anti-patterns and his chapter on using git with coding agents are what to read after this page.
You cannot recover from what you cannot read. Every move on this page starts with a judgement call: which changes were part of the task, which commit introduced the bug, is this diff the fix or a second bug. Willison’s anti-patterns list states the same rule for pull requests — never open one containing code you have not reviewed yourself — and every undo here rests on that same ability. Git hands you the evidence; reading it is still your job. An agent that writes code you do not understand has not removed the work — it has moved it to the worst possible moment, the one where something is already broken.
Go deeperOne agent per worktree, when you start running two at once
A worktree is a second working directory on its own branch, sharing the same repo history — so two agent sessions never overwrite each other’s files. Manually: git worktree add ../qairu-feature-a -b feature-a, plus git worktree list and git worktree remove. Claude Code wraps this as claude --worktree feature-auth, which creates .claude/worktrees/feature-auth/ on a branch named worktree-feature-auth and removes clean worktrees on exit. It is a fresh checkout, so you install dependencies again and ignored files such as .env do not come along (worktrees docs). Reach for this the first time two sessions collide, not on day one.
Stop. Do not change any more files.
1. Run `git status` and `git diff --stat` and show me the raw output.
2. For each changed file, tell me in one line whether it was part of the task
I asked for, or something you decided to change on your own.
3. Do not commit, and do not run git checkout, git restore, git reset or git clean.
I decide what gets thrown away.Put QAIRU Event Sign-up under git, then break it on purpose
Five moves. The first three set the repo up. The last two are the ones you will repeat for the rest of the course.
- In the project folder:
git init. Open the.gitignoreyou wrote in module 8 and adddist/and.claude/worktrees/next to.envandnode_modules/— before anything else. git add .thengit commit -m "chore: first working version". Check it:git statussays clean,git log --onelineshows one line.- Create an empty repo on GitHub, then
git remote add originwith the URL it gives you,git branch -M main,git push -u origin main. Refresh the page — your project now has an address. - The drill. Run
git checkout -b feat/seat-counter, then ask your agent to add a “12 seats left” counter to the sign-up page. Let it finish, thengit add .andgit commit -m "feat(signup): seats-left counter". - Now undo it:
git revert HEAD. Reload the page — the counter is gone, andgit log --onelineshows both the change and its reversal. Finish withgit checkout main. Your working version was never in danger, and you have now practised the exact move you will need on a bad day.
Set-up, the five daily commands, the undo table by situation, and the commit-message types — on one page you can keep open beside the terminal.