‹ Writing Claude Code

From the chat app to the terminal

Halfway through replacing every loading spinner in GameKal with skeletons, the coding agent I’d been working with in the Claude desktop app lost its hands. It could still read my files and talk through the plan. Every command it tried to run on my Mac failed. A few earlier conversations vanished from the sidebar, I restarted the app, I started a fresh chat, and the shell still wouldn’t come back.

The job didn’t stop. It shipped as release 0.1.26, along with a Safari calendar fix, a sign-in fix and enough else for 16 bug fixes and 9 enhancements in the changelog, with all 551 tests passing. The difference was where the building happened: in Claude Code, in a terminal, with the desktop app kept on as a second pair of eyes.

This post covers how that split worked, the commands and modes that turned out to matter, and the one setting I’d change before doing it again. It also covers the mistakes, because there were a few.

The day-to-day before

Most of my GameKal work ran through the desktop app, with Cursor alongside. I describe a change, the agent edits the repository through a connected folder, runs the tests and type check, and I review, commit and deploy. The repository’s CLAUDE.md spells out the rules: which commands an agent may run (the tests, the type check, four read-only scripts, read-only git) and which it never runs (builds, deploys, git writes, installs). I commit, deploy and build.

That setup depends on one thing: the agent’s shell on my Mac. When it went, so did the tests, the type checks and the ability to verify anything. Writing files through the app’s file bridge had a quirk too: a file sent back under a name it had used before could arrive with the old contents. From my side, finished work kept “reverting”, and the first theory was that another tool was editing my files. Nothing was.

Splitting the work in two

The fix was to stop asking one agent to do everything.

The builder lives in the repository. Claude Code in a terminal, started in the GameKal folder, with the same CLAUDE.md. It edits, runs the tests and the type check, and reports.

The reviewer measures and writes the prompts. The desktop app conversation still had the whole history of the job, could still read files, and could still drive a browser to measure the live site. It stopped editing entirely. Two agents writing to the same files is how you get reverts.

I carry messages and own anything irreversible. Commits, deploys, app builds and dashboard settings stayed with me.

One change, start to finish

The loop that shipped 0.1.26. Not to scale.

Each change
Measurereviewer, on the
live site or locally
Write the promptwith the numbers
and the cause
Build and testClaude Code
in the repo
Commit and deployme
Measure againreviewer, live

Big jobs went in as a file. For the skeleton work, the plan was a Markdown file in a git-ignored folder, split into stages. The prompt was one line: read the file and do stage 1. Smaller fixes went in as pasted prompts, and the good ones carried measurements: “87 scroll events in 5 seconds, each 370 px back”, not “the calendar is broken”.

The commands that mattered

None of these are secret, but I didn’t know half of them when I started.

Questions come back as choices. When Claude Code hits a decision it can’t make alone, it asks with numbered options, plus “Type something” and “Chat about this”. It’s fast and clear. It’s also one key away from the wrong answer: Enter picks whatever is highlighted, and I picked option 2 by accident once. Read the options before you press anything.

Typing while it works queues a message. A normal message sent mid-task waits until the current step ends. It doesn’t interrupt anything. That’s the right way to add the next item.

/btw is for questions, not work. It asks a side question about the current session without adding it to the conversation. The answer appears in an overlay, it can’t use tools, and it doesn’t change the task. I almost used it to send a list of new fixes, which would have been a nicely answered question and no work done.

! runs a shell command from the prompt. The output lands in the conversation, so the agent sees it. When the repository’s rules didn’t allow the agent to list my connected iPhone itself, it asked me to type ! xcrun devicectl list devices and read the result. It has to be typed in Claude Code’s prompt: in a normal shell, ! means something else.

/diff shows what changed. Files, line counts and the diff itself, without leaving the session. It’s the fastest review before a commit.

/remote-control puts the session on a phone. It shows a QR code, and the running session appears in the Claude app’s Code tab on an iPhone. You can read along, answer questions and send messages away from the desk while the work keeps running on the Mac. The Mac has to stay on.

Account skills aren’t terminal commands. My release checklist was a skill on my Claude account. In the terminal, /prepare-release came back “Unknown command”. The fix was to point Claude Code at the same checklist kept in the repository, which it could read like any other file.

Auto mode, and what a rules file can’t do

Claude Code has permission modes, and Shift+Tab cycles through them: the default asks before acting, accept-edits lets file edits through, plan researches without editing, and auto, where available, lets a classifier approve actions on your behalf. The footer says “auto mode on” when it’s on, and the log marks each step it approved.

Auto mode is pleasant. It’s also where I learned the difference between a rule and a guard.

A rules file is advice. My CLAUDE.md lists exactly what may run. Early on, with auto mode on, Claude Code edited files with sed -i and a Python one-liner. Neither is on the list. Nothing bad happened, but nothing stopped it either, and at one point I had to stop and ask whether it had installed something without asking me.

Permission rules are enforcement. Claude Code’s settings can deny specific commands, and deny rules apply in every mode, auto included. The list of things an agent must never run belongs there, not only in a Markdown file. Then auto mode can do what it’s good at, edits and tests, without being the last line of defense.

What went wrong

The builder acted before I approved. One prompt asked for a report on changing the app’s Supabase address, explicitly “report first, change nothing”. It made the change and reported afterwards. The change was correct and lived only in a local file until I deployed, but “report first” has to be a guard, not a hope.

The reviewer explained a real bug away. A measurement showed a game page 593 px shorter than its skeleton. The builder couldn’t reproduce it in a clean browser and suggested my browser was blocking images. The reviewer accepted that without asking for the one check that would have confirmed it. It was a real bug, and it cost an hour. That story is in The setting only production had.

Units drifted. Reports started saying “pt” for screen sizes. This is web and React Native, where the unit is px. Small, but it’s the kind of thing that ends up in code comments and then in someone’s head.

What I learned

Looking back, four lessons stand out.

Split measuring from building. One agent that measures and one that builds check each other. Each prompt carried the measurement that justified it, and each fix was measured again on the live site.

Encode the rules you can’t afford to have broken. If a command must never run, deny it in the settings. A rules file is for everything else.

Read before you press Enter. Multiple-choice prompts are quick, which is the danger.

Keep the irreversible steps for yourself. Commits, deploys, store builds and dashboard settings were mine the whole time, and that’s why none of the mistakes above reached a user.

What’s next?

The never-run list goes into Claude Code’s permission settings as deny rules, so auto mode can stay on. And the desktop app’s shell is worth reporting with specifics, in the same spirit as The AI that ate my file handles.

The app losing its shell felt like the end of the day’s work. It turned out to be the start of a better way to split it.

I'm listening if you're talking

A bug, a new build, a data question, a site that needs to be faster or a full rebuild. Tell me what's going on.

Let's see how to collaborate.