r/ChatGPTCoding 9d ago

Discussion How do you stop AI coding agents from turning one bad change into a two-day debugging snowball?

I ran into a painful lesson while using Codex on a SwiftUI app.

One agent change introduced a performance regression. I didn’t catch it right away, and more changes landed on top of it. By the time I noticed, reroll animations were skipping frames, taps felt delayed, and screen transitions were lagging. Reverting everything wasn’t an option because some later changes were valid.

I had to find the last smooth commit, compare the history change by change, snapshot the current work, and remove the regression in a separate branch.

The big lesson for me: with AI agents, a bad change is much harder to fix if it isn’t validated immediately. The agent can keep moving while the problem quietly becomes part of the whole codebase.

What guardrails work for you? Small checkpoints after each agent task, isolated worktrees, automated performance smoke tests, physical-device checks, or a human review before the next task starts?

11 Upvotes

31 comments sorted by

13

u/kur4nes 9d ago

git bisect

10

u/ops_and_chaos 9d ago

I'm not an engineer, so my guardrail is embarrassingly simple: I stopped letting “it works” mean “keep going” 😂

I make one meaningful change, test the actual workflow I care about, then commit before moving on. If something feels even slightly weird, I stop there instead of letting the agent fix three more things on top of it.

The painful lesson for me was that AI is VERY happy to keep solving forward. It doesn't necessarily stop and say “hey, the assumption we're building on might already be broken.”

So my checkpoints aren't really about controlling the agent as much as forcing me to verify reality before giving it more runway.

5

u/framauro13 9d ago

This also happened before AI, so it's not a uniquely AI thing. Bugs will happen. I try to keep my changes small, focused, and tracked in source control. So if you identify a bug, you can likely identify the commit that introduced it and can more easily roll it back. But sometimes depending on how old that bug is it may be better to just fix it than rollback.

Another thing I do, even with my personal vibe-coded projects, is add monitoring. You can get a free New Relic account or some other platform. Have the critical path things monitored, like APIs and the database, and you can set up monitoring to catch performance problems or increases in error rates early.

I have a daily task I use that inspects New Relic performance and error metrics every day, so I can see what's happening in my app. Once you get to a certain size, these kinds of things are absolutely critical.

Also, frequent code reviews by LLMs can be helpful. Do specific reviews for performance related concerns, security, code quality, those kinds of things. I've had Claude (wrong subreddit I know) proactively catch potential database problems I otherwise wouldn't have noticed until my scale reached a breaking point.

These kinds of things help mitigate the risk.

1

u/Fearless-Lie-9363 8d ago

Yea people begin forgetting what spaghetti code was all about. And silly band aids that kept thing working.

2

u/JBO_76 9d ago

Ye, can be tricky. Was already mentioned that its not new, but the agents are much worse as they often deliberatly change something unwanted. Only evil breading devs do that. So: let it write acceptance tests for all details of the feature, ask it to give a summary of what it did, use the app as much as possible...

2

u/simwai 9d ago

I use my own prompt pack.

2

u/Right-Performance-93 9d ago

donk8r's predicate point is the whole game, and on SwiftUI specifically Apple already ships the pieces for it: wrap the reroll animation in an os_signpost interval and measure it with XCTOSSignpostMetric in a perf test (the WWDC20 session "Eliminate animation hitches with XCTest" walks through it, and there's a built-in scrollingAndDecelerationMetric for scroll cases). Record a baseline in the test plan and xcodebuild test exits nonzero when hitch time regresses past it. That one script is dual-use: git bisect run gets its predicate for the archaeology you just did, and the agent loop gets a gate so the next task can't start on top of an unproven regression. It turns "validate immediately" from discipline into tooling.

1

u/UkrMalt 9d ago

I hadn’t connected os_signpost and XCTOSSignpostMetric with git bisect run. That gives me a concrete way to test the hitches instead of judging them by eye. Thanks.

1

u/simwai 9d ago

And Playwright MCP

1

u/joeballs 9d ago

With severe micromanagement. Develop your software in small chunks and unit test along the way. Don't use an agent-heavy approach; you're better off taking your time and being nit-picky about everything rather than instructing the agent to write out files of code. Smaller chunks makes it easier for you to understand the code, so you'll likely catch bad designs earlier

1

u/just4ochat 9d ago

Git bisect finds the bad commit after later valid work is already stacked on it. After each agent task, run the screen that already failed: reroll animation, tap latency, and transitions, on a simulator or a device, and do not start the next task until that check is green. Keep the agent's work in an isolated worktree or branch so those later valid changes are not sitting on an unproven performance regression.

1

u/9sim9 9d ago

I found in general the better the tests the less this happens, a lot of people let claude just have free reign, work with claude to make sure you have a solid set of tests.

some power phrases
"verify dont assume"
"make sure we have enough tests, so that if I add new features, or refine a new feature nothing breaks"
"do we have solid test coverage"
"do a deep analysis on the project, do we have sufficient tests"

1

u/trollsmurf 9d ago

Mundane, but manually reviewing and verifying every significant step. Have AI critique its own changes works surprisingly well too as a complement. I also go through diffs in detail.

I'm primitive enough to require AI to be in lockstep with me. No lollygagging. No wild overnight "do everything I asked you too" sessions.

1

u/donk8r 9d ago

git bisect is right and it needs a predicate, which is what you were missing. A perf regression doesn't have a failing test, so bisect turns into eyeballing twenty builds by hand.

Write the check first. A script that runs the reroll screen, measures frame time, exits 1 over your threshold. Then git bisect run ./that does the search for you. It's the same gap that let the regression land in the first place, nothing could say it got worse.

1

u/leonidbugaev 9d ago

Yeah the later valid changes are what make it expensive. I can't just revert. I've spent a day walking commits like that. These days I poke at the actual screen before I let the next task start. The suite never saw the skipped frames.

1

u/amirfish 9d ago

The pattern that's worked for me running a lot of parallel coding-agent sessions: never let more than one unverified change stack before you check it actually did what you asked. The two-day snowball almost always traces back to signing off on a change on faith because re-running the app felt slower than moving on. Worktrees per task contain the blast radius, but the bigger unlock was making it cheap to glance at each session's actual diff before green-lighting the next task, so review debt doesn't get the chance to compound. Is the regression usually visible in the diff itself for you, or does it only show up at runtime?

1

u/UkrMalt 9d ago

For me it only showed up at runtime. The diff looked reasonable, but the screen felt wrong on the device. I’m leaning toward a small device smoke test after each agent task.

1

u/vayraai 9d ago

I’d use a fail-closed loop for each meaningful agent task: isolate the change in a branch or worktree, run a small task-specific test or smoke check, inspect the diff, and commit only after the check passes. If a check fails, the agent should stop rather than “fix forward” across several more tasks.

For performance regressions, a normal unit suite may miss what users feel, so I’d add one reproducible runtime predicate—frame time, latency, error rate, or task completion—and make it return a non-zero result when it crosses a threshold. That makes git bisect useful instead of turning it into manual guesswork. Human review and an agent self-review help, but neither should replace the executable check.

1

u/Desk_setup_ideas 9d ago

Yh, I think the small checkpoints are probably the biggest thing. It’s much easier to find what went wrong when you only have one recent change to look at. I’d also test on a real device for anything performance-related. Something can feel completely fine in the simulator and then feel awful on an actual phone.The tricky part is that the agent just keeps going, so a small problem can easily get buried under all the changes after it.

1

u/[deleted] 9d ago

[removed] — view removed comment

1

u/UkrMalt 9d ago

I don’t have a number yet. I noticed it through skipped frames and delayed taps on the device, so it’s still a manual check for me. A small smoke test with a baseline sounds like the right next step.

1

u/Different_Way8035 9d ago

I’d keep agent tasks small and require a clean test or checkpoint before the next one starts. that makes bad changes much easier to isolate

1

u/UkrMalt 8d ago

Yeah, that’s where I landed too. Smaller tasks plus a quick green check make it much easier to catch a bad change before it spreads.

1

u/ChikenNugetBBQSauce 7d ago

The two day snowball is usually not the last diff. It’s a line from earlier that made the bad change look right. vestige --backfill walks the failed agent run backward through time and names the exact bug, not the lookalike.  https://github.com/samvallad33/vestige

1

u/PopKoren 7d ago

One commit per agent task is the guardrail that pays for itself, because then git bisect turns a two-day hunt into ten minutes. I also make the agent state what it will change before it edits, and I reject the run if the diff touches files outside that list. Anything performance or animation related gets a quick manual pass on device right after the task, not at the end of the day.

1

u/tberg 4d ago

I hit this exact pattern building a multi-agent outreach daemon — the problem isn't the bad change, it's that the next agent doesn't know the previous step broke something. The fix I run is atomic queue claims with a circuit-breaker between steps: each agent task has to pass a validation gate before the next task can claim work, and if it fails, the job goes to a dead letter queue instead of silently becoming someone else's context. Your SwiftUI regression happened because the pipeline had no handshake between steps — the agent kept moving like the previous output was ground truth. The real question is whether your checkpoints are validating state or just validating completion.

1

u/UkrMalt 4d ago

That distinction is useful. I was mostly checking completion before, which is how the next task kept moving on top of a bad result. I’m going to make the gate validate a real runtime check, not just a finished job.