My Claude Code and Codex Workflow on an Existing Codebase

How I work with coding agents in an existing codebase, why I make them stop before writing code, and why we still review every PR by hand.

Hand checking off items on a to-do list

In my six-months post I wrote about AI letting me move faster than my understanding. I have thought about that a lot since, mostly because using these tools at work makes me deal with it every day.

A few weeks ago I was setting up Dependabot on three of our repositories. It’s the GitHub bot that opens a PR when one of your dependencies has a known vulnerability. Part of it needed repository settings changed, and the agent gave me step-by-step instructions to go and change them myself. I didn’t think I was allowed to, so I asked it to check. I wasn’t. Those settings need admin access and I only have the role below it.

So we sent a message to our GitHub admin instead, with a line asking them to check the branch protection rules while they were at it. Afterwards the agent decided that line had been unnecessary. It had asked GitHub, got a 404 back, and concluded there were no rules. I said maybe I just don’t have access. It went and read the docs, and that was it: GitHub returns the same 404 whether the rules don’t exist or you aren’t allowed to see them. It had turned “I can’t see anything” into “there’s nothing there” and reported it as something it had checked.

Nothing broke, and the message had gone out with the right line in it. But it was the second confident wrong answer in the same session, and both times I only caught it because something sounded off to me.

I use Claude Code and Codex. A fair amount of my work now is asking an agent to investigate something, discussing the approach with it, and then checking what it produced. So I wanted to write down what that looks like right now. Partly because it might help someone in a similar spot, and partly because explaining my own process is usually how I find the bits I haven’t thought through.

Writing down the things I kept repeating

The repositories I work in have a lot of history. Shared components, old patterns that are still everywhere, decisions that only make sense once you know what the product was doing when someone made them.

An agent gets moving very fast in that environment. It also happily creates a new component when we already have one, suggests an import that doesn’t work in this particular setup, or cleans up something that turns out to be necessary.

I got tired of making the same corrections, so I keep a shared AGENTS.md across the work repositories. Both agents read the same instructions. Commit conventions live in a separate file, and there’s another one that lists the incidents behind each rule. “Verify it at runtime”, for example, comes with a note that we diagnosed a visual bug from the stylesheet more than once and only found the real cause after checking computed styles in the browser.

The workflow has six stops: trace, options, report, tests, cleanup, and PR description. A slightly formal way of writing down where I want the agent to pause and what I need to know before we carry on.

Understanding the ticket before touching anything

Most tasks start in Zoho Projects, and a lot of what a ticket actually says lives in its screenshots. The bug, or the design someone wants, is in the picture, and the text around it is often a line or two. For a while I was copying tickets into the prompt by hand, which left the images behind.

So I built a skill for it. It talks to the Zoho API with an OAuth token I set up for myself (a “Self Client” in Zoho’s API console), which lets it read tasks, comment on them, create new ones, and download the images embedded in a description. The agent saves those images to a scratch folder and opens them like any other file, so it’s looking at the same screenshot I am. It also caches the access token for its full hour, because Zoho limits how often you can ask for a new one and an early version kept dying halfway through a batch of tasks.

The first thing I want back is the agent’s version of the problem, before it opens any code. What does it think the ticket is asking for? What should happen, and what happens instead? If its reading doesn’t match mine, we’ve found the misunderstanding while it costs one message to fix. It also leaves a written record. When I write the PR description, or someone asks a couple of months later why we changed something, the agent’s reading of the problem and my corrections to it are there, and I don’t have to rely on memory.

If the problem still feels fuzzy after that, I use a grilling skill. It has the agent question me about the request in rounds, each question with its own suggested answer, until we’re sure we understand it the same way. Sometimes I’ve described the symptom badly, or I’m calling something a bug without having checked what it was supposed to do. It feels slow when I think I already know the answer, but finding out I asked for the wrong change after it’s been implemented is a lot more annoying.

Only then does it trace the actual code. Where does the data come from? What transforms it? Which component ends up showing it? Is there already a helper for this? What else uses the same thing?

It reports the path it followed, the files it expects to change, and what it still doesn’t know. File references have to come from code it actually opened, because a plausible filename is very easy to mistake for investigation. That last part, saying what it doesn’t know, is where something like the 404 is supposed to come out.

Picking an approach while it’s still cheap to change

Once we agree on the problem, I want to see the reasonable ways to solve it. Usually there are a couple. A local change might be enough, or the problem might really belong in a shared component. Moving some logic could make the next change easier and also affect callers outside the ticket.

I ask for those costs to be spelled out, including for the cheap option, because agents tend to reach for the bigger refactor. If there’s only one sensible approach, fine. I don’t need three options when two exist just so the recommendation looks good.

After choosing, we sketch how the work should split into commits and start a fresh branch from develop. The split is mostly for the reviewer. With the amount of technical debt we have, someone still has to check every change by hand, and that’s much easier when each commit does one thing: the refactor in one, the behaviour change in another, the cleanup in its own. Planning it this early is also often when I notice that a supposedly small task has several things mixed into it.

Cleaning up as we go

In my first-weeks post I was very careful not to touch anything beyond the immediate request. That made sense when I was new to the codebase. Now we clean up as we go.

If something in the code we’re already working in is worth fixing, isn’t tangential to the task, and still fits in what a reviewer can reasonably check in one PR, it goes in, in its own commit. Reusing a shared component, pulling out a duplicated helper, removing dead code. Ease of review is still the limit.

If it’s worth doing but too big for this PR, we create the ticket ourselves with the same Zoho skill: what we found, where it is, and why it’s worth doing.

Once the agent has implemented the change, I want a report of what it did and why, especially where it departed from what we agreed. I also want to know what it checked and what it didn’t. “I didn’t run that” is useful. A vague “everything works” just makes the review harder.

Tests that would have caught the problem

One incident in the rationale file was a test meant to protect a change involving an email address. The fixture was already lowercase, so it passed against both the old code and the new code. A green test that never exercised the difference it was supposed to check.

That’s why I ask for regression tests to be run against the old code too. A test for the behaviour we changed should fail there. A test for something we meant to keep should pass. It’s a way of checking the test itself, which matters when an agent can write a convincing-looking suite in a minute.

For components with no tests at all, I also want coverage of the states and actions a user can actually reach, starting with the main flows and whatever the change affects. Otherwise you end up with one test around the new line and nothing protecting the old component around it.

Visual work needs a different kind of checking. We’ve had cases where reading the CSS produced a confident explanation that didn’t match what the browser was rendering. So I want computed styles, real interactions, and checks at the viewports the change could affect. Fixing the screenshot from the ticket tells me nothing about whether we broke a narrower screen.

Explaining the result

Dead code only gets removed if we can prove it’s dead. Some styles once looked unused because their names never appeared as literal references, but a component picked them dynamically through styles[variant]. Deleting them would have killed working hover states. Searching for a name helps, but you still have to understand how the code gets to it.

Then the PR description. I use a skill for that too, followed by a humanizer pass, because otherwise I get a lot of text announcing fairly ordinary changes like a major engineering achievement. I just want someone to be able to read it and understand the work.

Visual changes get screenshots, and I got picky about those too. The agent opens the app in a headless browser, fixes the viewport before the first shot so before and after have the same size and the same data, and saves real PNG files. GitHub’s CLI can upload attachments now, so it puts them on the PR itself, and I don’t spend time dragging images into the web editor. Before and after sit side by side in a table, sized by width only, because two screenshots of the same screen are rarely the same height and forcing one height squashes one of them. If a change takes a few steps to see, it records a GIF of the flow.

The reviewer needs the problem, why we chose this approach, and where the verification stops. They weren’t in the conversation while we ruled out the other options, and a description that only narrates the diff makes them do that thinking again. The PR then gets linked back to the task so you can follow the work from either side.

Why we still review every PR by hand

With all that written down, it might sound like we’re close to handing over a ticket and letting the whole thing run on its own. We’re not, and the amount of technical debt is a big part of why.

Too much behaviour still needs investigating. The existing checks don’t describe everything the product is supposed to do, and some dependencies between parts of the code only show up when you follow a specific flow. I wouldn’t be comfortable treating a finished agent run plus green CI as enough to merge.

What we’re working on is making more of the process deterministic through tools and CI. Repeatable checks that run and give results we can inspect, so we depend less on an agent remembering an instruction or a reviewer spotting the same mistake again. Instructions describe how we want to work. Turning a repeated check into tooling is more dependable than asking for it in prose every session, and there’s still plenty to do there.

PRs need manual review today, and I expect they will for a long time. Someone still has to check the behaviour against what the ticket actually wanted, question the scope, and decide whether a new abstraction will make the code easier to live with. Better automated checks should leave more attention for that.

The part I still have to do

All of this costs something. Reading reports takes attention. So does checking whether a confident explanation is backed by evidence. Maintaining the instructions can turn into its own little project, and I have to be careful that I’m improving the work and not just adding process around it.

A typo doesn’t need the same conversation as a change to a shared data flow, so the early stops get skipped for genuinely trivial stuff. For everything else, I’m still working out how much structure earns its keep.

I do like that the workflow gives me specific places to stop and think. I can ask for help tracing something unfamiliar, then check that I understand the explanation before agreeing to change anything. That helps with getting the ticket done and with learning the codebase.

The question from the earlier post hasn’t gone away, though. If I came back to this change in a month without the chat history, would I understand why we made it and be able to work on it again? That’s probably the thing I most need to keep an eye on while I keep changing how I work.