Thursday, July 09, 2026

Delphi and AI[11]: Debugging with Claude Code

Most of the noise about AI and coding is about writing new code. Just check all the examples in the recent Kai release video. Indeed, this is probably the best idea to explain what LLMs can do to a random programmer, but to me the biggest help I get from them is one the other end – not while writing new code but in debugging old, large, sometimes multithreaded Delphi programs. The kind of codebase that no longer fits in one human head, where a crash reproduces once a month on one machine in the field, and the stack trace is forty threads deep.

That’s where Claude Code really shines – as a code investigator tool. This post is a tour of how I use it for debugging, and, just as importantly, how I keep it under control.

I do specifically use Claude Code for all my debugging needs but this article is not tightly coupled to it. Most of the ideas apply the same way to other LLM-based coding tools – Codex, Gemini CLI etc.

Before you start

Before you start sending proprietary code to a cloud model, check where your code will go. Most importantly – check whether it will be used for training new models. If so, the code you are submitting may at some point be used to generate an answer for another user therefore exposing proprietary code. Even worse, you will never know which parts of code will go to the cloud. Claude Code will analyse the program, walk your unit tree and send parts that it seems to be interesting (in the context of solving your problem) as part of prompts to the cloud LLM engine. If you are working under an NDA – and your privacy is not respected on the cloud – this represents a serious problem. 

For Claude Code, go to claude.ai, then check Settings → Privacy. Even if you are already using any of LLM cloud providers for analysing code, do the same. Settings may have changed since the last time you verified. 

Be aware of the cost. Cloud LLMs are metered tools and prices change constantly. Set a budget, watch it, and don't be surprised when the number that was true last quarter isn't true today.

If privacy or cost present a big problem, you can try running Claude Code locally. For example, Qwen3.6-27B model served by local Ollama fits into 24 GB of GPU memory. Once you have it installed locally, run Claude Code with ollama launch claude (https://ollama.com/blog/launch). There is a trade-off, of course. Your local model will be slower and less capable than cloud LLM running a frontier model, but maybe it will serve your needs.

Talking to an LLM

When describing a problem to Claude Code, keep a few guidelines in mind. Most importantly, be specific. Don’t say “fix my program, it crashed” but “when I do X, Y happens, but Z should happen instead, please find why”.

If a problem is of a more complex nature, ask Claude Code to start with a plan. Instead of saying “here’s a bunch of stack exception logs, find a problem” it is better to write “here’s a bunch of stack exception logs, make a plan for analysing them and extracting common problems, then start working on them”. The superpowers plugin is a good help with such kind of work.

If you don’t know how to approach a complex task, describe it to a LLM and ask it to write instructions for Claude Code. You can do that in Claude Code, online Claude, or any other LLM. Just say “I have a bunch of stack exception logs; please generate a prompt for Claude Code that will instruct it to analyse these logs and fix the reason for crashes”. Then paste the output into Claude Code.

If you are working on a very open problem that you don’t know how to approach (for example, you – or Claude - have to write some code to fix a problem and you’re not sure what would be the best approach), use the brainstorming plugin. It helps Claude Code exploring possible options in an organized manner.

Clear the context often. Long sessions drift. When you switch problems, run /clear or restart Claude Code. When the session becomes very long, do a /compact.

And most importantly: Don’t trust the answers! An LLM will always produce a confident, coherent story — and that story may simply not be true. As a first step, always ask LLM: “Is this really true? Does your answer make sense?” If the AI wrote a fix for a problem, ask: “Is this a correct fix for this problem? Will it break backward compatibility? Will it introduce any additional problems?”

After you get new, verified answer, you should still not trust it. Always do your own testing!

The debugging playbook

My use of LLMs for debugging can be split in categories:

  • Debugging helper — AI helps in understanding unfamiliar code, finding the reason for a bug, and proposing and verifying a fix. 
  • Stack-trace analysis — feed an LLM one or many stack traces and let it find the common cause of a crash or deadlock.
  • Memory-leak analysis — hand it a FastMM4 full-debug leak report and let it trace the allocation back to the logic error.
  • Preemptive debugging — audit uncommitted changes or a whole codebase *before* the bug reaches production.
  • Writing tests and documentation — LLMs are great at writing unit tests, stress tests, and prose that explains how the parts interact.

The four stories below are all real sessions on production Delphi code. I've compressed them, but the reasoning is the AI's.

1. Analysing a deadlock that was already fixed

I received an archive with 40+ stack trace dumps in multiple files spanning half a year. Stack traces contained dozens of threads each. From the user observation it seemed that the software was hanging on shutdown.

I asked Claude Code to survey the dumps and make a plan first rather than diving in. It counted the distinct dumps, catalogued every thread type, and proposed a six-step investigation: focus on the shutdown dumps, find threads blocked on lock acquire (not just a normal `WaitFor`), correlate thread IDs across consecutive dumps to separate "momentarily blocked" from "genuinely stuck," and trace lock ownership.

Working through it, it found the pattern: two worker threads that could deadlock. It, however, correctly noticed that the problem should not occur in the current code. Next it found that stack traces were not generated with the latest software version and it started looking into SVN history. Quickly, it found that the cause has been fixed months earlier. The deployed binary had simply never been updated past the buggy build.

There was no fix necessary, just a gentle reminder to the customer to update their software. Still, Claude saved me from at least a day or two of browsing the stack traces and scanning the code.

2. Find → fix → verify

In the same product, a different customer had a different problem. The app blocked during audio-capture shutdown. I asked Claude to explore the reason.

It read the stack trace and the source and laid out a textbook lock-ordering deadlock: two threads were waiting on one another to finish the work before exiting. Claude proposed the fix: stop the capture thread before taking the lock.

Nothing special so far. The valuable part came next. I asked it to "check whether its own proposed change introduced any new problems, and to re-examine the locking with the fix applied."

It found a second deadlock — one the original crash had never triggered, only avoided by luck.

This was a very unexpected – but also much appreciated – side effect of verifying the fix!

3. Verify the fix — then verify it again

A WebSocket connection was vanishing: gone from `netstat`, but not a single error in the logs. The implementation lives in my WinAPI wrapper unit which I analyzed with Claude Code.

Claude sent two explorer agents into the codebase in parallel and came back with a ranked list of causes. The top one was the culprit: when WinHttpWebSocketReceive failed, the code cleared its "receiving" flag and returned `false` — without setting an error state, firing a callback, or logging anything. The connection was dead and nobody upstream ever found out. A few sibling issues had the same shape (a server-initiated close frame with no data, silently swallowed).

It applied four fixes, all propagating the real OS error code and message up through the existing callback chain. Good.

Then I asked it to log the change in the unit header and re-check everything it had just written. And it caught its own mistake:

The first version of the fix called Winapi.Windows.GetLastError to get the error code. But WinHttpWebSocketReceive returns the error code directly — it doesn't set the thread's last-error at all. So GetLastError would have reported a completely unrelated value.

It rewrote the fix to capture the function's actual return value. Amusingly, this is exactly the kind of GetLastError trap I have a standing rule about in my own coding guidelines — and the review is what caught it. Thanks, Claude, for making me follow my own rules!

4. A multithreaded leak

The last one is the hardest category: an intermittent memory leak in GpEventBus, my thread-safe event bus. GpEventBus was entirely written with Claude Code (see Introducing GpEventBus: Thread-safe event bus with synchronous event dispatch). We tried to do enough unit testing to find all the problems before release, but multithreading is just too hard and some errors only become apparent later.

In this case, FastMM4 full-debug mode gave me a leak report with an allocation stack trace; I handed it to Claude and asked it to find a reason for the memory leak. Claude explored the leak report, source code and documentation and managed to pull together a chain of events that explained how cross-thread dispatching in combination with subscriber threads being terminated caused this leak.

Rather than believing it or checking the proposed fix, I asked it to write a unit test that exposes the problem first. AIs are great at writing unit tests, especially the nasty ones that have to get multiple threads into correct state for the bug to appear.

Only after the test was written – and demonstrated the problem – we continued with fixing the code. 

Preemptive debugging

The best use of AI for debugging is that you don’t have to wait for a crash. LLMs are great at code analysis and guessing what could go wrong simply by looking at the code.

You can use them before you commit. Simply write a prompt like: “Are my uncommitted changes in this folder OK?” You can also set up a post-commit hook on your repository that checks all commits with an AI.

You can also use LLMs for a whole-codebase audit. Ask it to analyse a project for “bugs of all kinds” and it will find many. As this will be a complex problem, it is always good to start static analysis with a plan. I went a step further and asked online Claude to write me a good static analysis prompt, which you can download here.

Don’t take results from this prompt as a real bug report. At least do the next step of filtering and ask Claude: “I have a list of potential bugs described HERE. Please analyse each one and check if this is a real problem or false alarm.”

Expect a ton of problems to be reported in your own code. Some will be extreme corner cases which are never hit, but some may represent a real, undiscovered bugs, that are just waiting to strike. I used this on OmniThreadLibrary, which is a stable, well-tested library but still it found a TON of problems that were just never discovered. All of them were recently merged into codebase.

The takeaway

Strip away the specifics and the workflow is always the same:

1. Describe the symptom precisely — what you observe, not what you think the fix is.

2. Let it investigate — plan first for anything complex, and give it the source, the logs, and the docs.

3. Always verify. The AI is confident whether or not it's correct. Read the reasoning, check the code.

4. Make it test its own fix — re-review the diff, re-run the analysis, run the unit tests.

Used this way, Claude Code doesn't replace the debugging skill you've built over the years. It amplifies it. It reads forty-thread crash dumps without getting tired, remembers the SVN commit you forgot, and catches the second deadlock hiding behind the first — as long as you stay the engineer in the loop, and never take "I fixed it" on faith.

---

This post accompanies my "Delphi Bugs Meet AI" talk at DelphiDay 2026, Piacenza. See the download link in the Presentations section for more examples.

No comments:

Post a Comment