# Let the AI Try Something Reckless: On a Branch There's a specific flavor of hesitation I want to talk you out of. You've got an idea (*rewrite the storage layer*, *try a completely different CLI structure*, *add a feature that touches four files*) and you suspect the AI could just do it. But you're not sure it'll work, you're not sure you'll like it, and the thing it'd be operating on is your actual, working code. So you don't ask. Or you ask, get a sprawling multi-file change back, and now you're squinting at it going "...how do I undo all of *this* if it's wrong?" That hesitation is the tax you pay for not having a sandbox. This post is about removing it. If you're new here: this is part of [The Workflow](https://git.jpaul.io/justin/ai-workflow-course), a free course about all the engineering scaffolding *around* AI-generated code (the version control, the editor integration, the review reflex) that the model itself doesn't give you. A couple of posts back we [installed the safety net](https://git.jpaul.io/justin/ai-workflow-course): Git, framed as undo for the AI. That safety net was perfect for *one* bad edit: commit, then `git restore` if the AI makes a mess. Today we go one size up: isolating a *whole line of experimental work* so you can keep it or throw it away as a single unit. That's a branch. ## What a branch actually is (it's less than you think) Strip the mystique and a branch is **a named, movable pointer to a commit.** That's the entire definition. Your commit history is a chain of snapshots; you built that intuition with `git commit`. A branch is just a sticky label that points at one of those snapshots and slides forward every time you commit. When you ran `git init -b main` to start your repo, Git made one branch for you and named it `main`. Every commit since moved the `main` label forward. You've been "on a branch" this whole time without thinking about it. Here's the part that surprises people with an ops background, because it cut against my instincts too: **creating a branch copies nothing.** No second folder. No duplicated files. No disk cost worth mentioning. Git writes a new label pointing at the same commit you're standing on, and that's it. Which is exactly *why* branches are cheap enough to be disposable, and disposable is the whole property we're after. ```bash git branch # list branches; the * marks the one you're on git switch -c experiment # create a branch called "experiment" and switch to it git switch main # switch back to main git branch -d experiment # delete a branch you've already merged git branch -D experiment # FORCE-delete a branch, merged or not (the "throw it away" button) ``` (Quick aside for anyone who's googled this before: you'll see `git checkout -b experiment` all over the internet. It does the same thing as `git switch -c experiment`. The newer `switch`/`restore` commands got split out of the old overloaded `checkout` so they actually say what they mean. Use whichever you like; I'll use `switch`.) ## The reframe: a branch is a scratch VM you can blow away You already have the mental model for this, you just file it under different names. A branch is the Git equivalent of **a scratch VM you snapshot and roll back, a staging box nobody depends on, a feature flag you can rip out.** You spin one up *precisely because* you're about to do something you might regret, and you want a clean way to make it never have happened. Picture it as two tracks: ``` main: A───B───C (always runnable; your "known good") \ experiment: D───E───F (the AI's bold attempt, however messy) ``` While you're on `experiment`, `main` is frozen at C: runnable, shippable, untouched. The AI can leave `experiment` as a smoking crater at F and `main` genuinely does not care. When you're done, you make exactly one decision: - **Keep it:** merge `experiment` into `main`. C gains D, E, F. - **Kill it:** delete `experiment`. D, E, F evaporate. `main` is still exactly C, as if nothing happened. That second path (*kill it, no trace*) is the one this whole concept exists for. It's the difference between "I now have to carefully undo everything the AI did" and "I delete the branch." One more thing that feels like magic the first time: when you `git switch` to another branch, **Git rewrites the files in your folder to match it.** Switch to `experiment` and the AI's half-built feature appears in your editor. Switch back to `main` and it vanishes. Same folder, different contents, instantly. (This is also why Git won't let you switch with uncommitted changes that'd get clobbered; switching would silently throw work away. The fix is the habit you already have: commit before you switch.) [insert a screenshot referencing `git log --oneline --graph` showing main and an experiment branch diverging here] ## The lab: let the AI go bold on `tasks-app` Enough theory. The course runs on a tiny example app called `tasks-app` (a little command-line to-do tracker), and this is where branches stop being abstract. Make sure you're on a clean `main` first (`git status` should say "nothing to commit"), then spin up an experiment: ```bash cd ~/ai-workflow-course/tasks-app git switch main git status # must be clean git switch -c experiment/priorities git branch # the * is now on experiment/priorities ``` Now give your editor-integrated AI a deliberately *bold* task, the kind you'd hesitate to run straight on `main`: > *"Add task priorities (low/medium/high) to this app. Store a priority on each task, let me set it when adding (`add "thing" --priority high`), show it in `list`, and sort `list` so high priority comes first. Change whatever files you need to."* Let it edit `tasks.py` and `cli.py` freely. This is a multi-file change: exactly the kind that's nerve-wracking on `main` and completely relaxed on a branch. Review what it did, then commit **on the branch**: ```bash git diff # read what it actually changed python cli.py add "ship module 6" --priority high python cli.py add "water plants" --priority low python cli.py list # see if priorities work and sort git add . git commit -m "Add task priorities (experiment)" ``` The payoff: prove the isolation. Switch back to `main` and watch the whole feature **disappear**: ```bash git switch main python cli.py list # no priorities: main is exactly as you left it ``` Sit with that for a second. Your bold change exists *only* on the branch. `main` never saw it. That's the entire point of the module in two commands. ## Decide its fate: keep it or kill it **Keep it (merge):** switch to the branch you want to *receive* the work, then merge: ```bash git switch main git merge experiment/priorities # likely a fast-forward: main slides up to the branch git log --oneline --graph # straight line = fast-forward python cli.py list # the feature is now on main git branch -d experiment/priorities # branch did its job; -d is the safe delete ``` Worth knowing there are two flavors of merge, and Git picks for you. If `main` hasn't moved since you branched, you get a **fast-forward**: Git just slides the `main` label up to F, history stays a straight line. If `main` *did* move on (you committed to it while the experiment was off doing its thing), the two lines diverged and Git stitches them with a **merge commit** that has two parents. You don't choose; you just recognize them in the graph (straight line vs. a visible fork-and-join). **Kill it (discard):** this is the one I really want you to feel. The AI tried something, you looked, you don't want it. You don't undo anything. You don't `restore` file by file. You switch away and delete: ```bash git switch main # files snap back to known-good main git branch -D experiment/priorities # force-delete the unmerged branch git log --oneline # no trace of the experiment on main ``` That's it. Notice what you did *not* do: no file-by-file restore, no manual undo, no hunting through diffs. You deleted a label and the entire experiment was gone. **The whole bold attempt cost you one branch and one delete.** This is the mental shift the module is selling. When discarding is *this* cheap, you stop being precious about what you let the AI try. Risky refactor? Branch it. Want to compare two approaches? A branch each; keep the winner, delete the loser. The branch becomes your unit of "maybe." ## Merge conflicts: when two changes collide (and the AI resolves them before you see them) Most merges just work; Git is genuinely good at combining changes that touch *different* lines. A **conflict** only happens when two branches changed the *same* lines in different ways, and Git refuses to guess which you meant. It stops and marks the collision right inside the file: ```python <<<<<<< HEAD print("usage: python cli.py [add