You Got 70% of Your Code Converted With AI. Now What?

by DeeDee Walsh, on Apr 14, 2026 1:16:43 PM

A field guide to the last 30% and why it's where modernization projects live or die.

You pasted your VB6 module into Claude. Or maybe ChatGPT. Maybe Copilot. And what came back looked... surprisingly good.

The syntax was clean. The structure made sense. It even compiled on the first try or close to it. You started thinking: maybe this won't be so bad. Maybe we can knock this out in a couple of sprints.

That was three months ago. You're still not done.

If this sounds familiar, you're not alone. We talk to engineering teams every week who are living some version of this story. They got the exciting 70% for free, or close to it, and now they're buried in the other 30%, which turns out to be the part that actually matters.

This post is for those teams. Not to sell you anything, but to give you a map of the terrain ahead, based on what we've learned modernizing millions of lines of legacy code across VB6, PowerBuilder, ASP.NET WebForms, and others. Because the last 30% isn't random. It's predictable. And once you know what to look for, you can at least stop being surprised by it.

Why the first 70% feels easy

Generic AI models (Copilot, Claude, Codex) are genuinely good at translating code syntax. They've ingested enormous amounts of source code in every major language, and they can pattern-match their way through straightforward conversions with impressive accuracy.

Here's what they handle well:

  • Basic syntax translation. Variable declarations, loops, conditionals, simple class structures — the grammar of the language gets mapped correctly most of the time.
  • Standard library equivalents. MsgBox becomes MessageBox.Show(). Left$() becomes Substring(). These are lookup-table problems, and LLMs are great at lookup-table problems.
  • Surface-level structure. The AI will break your monolithic form into something that looks like a reasonable C# class. It'll generate method signatures, constructors, property accessors.

This is real, legitimate progress. The problem is that it creates a dangerous illusion of completeness. The code looks modern. It reads like it should work. But looking like C# and behaving like the original application are two very different things.

The taxonomy of what breaks

After analyzing thousands of modernization engagements, we've found the failures in AI-generated code cluster into a few predictable categories. Here's your field guide.

1. Error handling semantics

This is the single most common source of behavioral regressions, and it's almost invisible in a code review.

VB6's On Error Resume Next doesn't just skip errors, it creates an entire execution model where code continues flowing through failure states, checking Err.Number at arbitrary points downstream. The application's business logic often depends on this behavior. Records get skipped, defaults get applied, fallback paths get taken. All silently, all by design.

When an LLM converts this to C#, it typically does one of two things, both wrong:

It wraps everything in try-catch blocks, which changes the control flow. Code that previously continued executing now jumps to a catch block. Side effects that occurred between the error and the check point no longer happen.

Or it removes the error handling entirely, assuming it was sloppy code. Sometimes it was. Sometimes it was the only thing preventing a cascade failure in production at 2 AM.

The fix isn't mechanical. It requires understanding intent. For each On Error Resume Next block, someone has to determine: was this defensive programming, intentional flow control, or a genuine bug that happened to work? That's more than a syntax question. It's an archaeology question.

2. Data access layer translation

Your VB6 or PowerBuilder application doesn't just use a database, it has an intimate, idiosyncratic relationship with one. ADO RecordSets, embedded SQL strings assembled through concatenation, cursors that walk through result sets row by row with side effects at each step, DataWindows with retrieval arguments and embedded business logic.

An LLM will convert ADODB.RecordSet to SqlDataReader or maybe Entity Framework. And the resulting code will often be functionally incorrect in ways that only surface with production data.

Here's a common example: VB6 RecordSets are disconnected by default after population. Code routinely modifies them in memory, passes them between functions, and writes them back. The AI-generated equivalent using SqlDataReader is forward-only and connected — fundamentally different semantics. Your code compiles but now throws exceptions on the second read, or worse, silently returns stale data.

Then there's the SQL itself. Legacy applications often build SQL dynamically including string concatenation based on user input, runtime conditions, configuration flags. The AI might "modernize" this to parameterized queries (which is the right security practice) but break the actual query logic in the process, because the original string building contained implicit type conversions, null handling, or conditional joins that don't survive translation.

3. COM, ActiveX, and the third-party dependency cliff

Every substantial legacy application has dependencies that don't exist in the modern world. COM components. ActiveX controls. Third-party grids, reporting engines, and data-aware controls that were purchased from vendors who went out of business in 2009.

The AI doesn't know what MSCHRT20.OCX does. It doesn't know that your VSFlexGrid isn't just a grid; it's a grid with 47 custom event handlers, embedded formatting logic, and cell-level validation that your users rely on daily. It'll generate a stub, or map it to a modern control that handles maybe 60% of the surface area.

This is where engineers lose weeks. Each third-party component becomes a mini-project: evaluate the modern equivalent, determine which features are actually used, build an adapter or wrapper, test against real user workflows. The AI gave you a TODO comment. You need an engineering plan.

4. Implicit type coercion and Variant behavior

VB6's Variant type and its implicit coercion rules are, to put it diplomatically, creative. Null + 1 doesn't throw, it returns Null. An empty string compared to zero evaluates to True. Date values silently convert to doubles and back.

Your application almost certainly depends on some of these behaviors, and nobody documented which ones. The AI will convert Variant to object or dynamic in C#, which gives you a type but not the same runtime semantics. The resulting code might pass unit tests and fail catastrophically on edge cases that only appear with real data. A customer with a null middle name, an invoice with a zero quantity, a date field that's actually empty.

5. State management and form lifecycle

VB6 Forms and PowerBuilder Windows aren't just UI, they're stateful containers. They hold module-level variables, manage their own lifecycle events, maintain references to other forms, and often serve as ad-hoc data caches. The form is the application architecture.

An LLM will generate a WinForms or WPF class that looks structurally similar but doesn't replicate the lifecycle. When does the form initialize its data? What happens when it loses focus? What's the order of event firing during close? These behaviors are framework-specific, and the AI's target framework doesn't fire events in the same order as the source.

The result: the application mostly works, except for a set of timing-dependent bugs that are miserable to diagnose because they only appear during specific user interaction sequences.

6. Business logic buried in unexpected places

This is the category that isn't really about technology at all. It's about the fact that legacy applications accumulate business logic in places no one expects.

Report formatting code that applies rounding rules specific to a regulatory requirement. A print preview function that also calculates tax. A grid's BeforeUpdate event that enforces a business rule that exists nowhere else in the system. No documentation, no specification, just that one event handler written by someone who left the company in 2014.

The AI converts the code faithfully. The business logic is technically still there. But it's embedded in a component replacement that nobody thought to test against the actual business rule, because nobody knew the business rule lived there.

The workflow problem

Beyond the technical failures, there's a more fundamental problem with the "paste it into AI" approach that doesn't get talked about enough: it produces a terrible engineering workflow.

When you write code from scratch, you build a mental model as you go. You understand your own design decisions. You know where the tricky parts are. You can debug efficiently because you know the architecture.

When you debug AI-generated code, you're doing something much harder: reverse-engineering the intent of code written by a system that had no intent. The AI made choices, which pattern to use, how to handle a particular edge case, what to name things but those choices weren't reasoned. They were probabilistic. There's no design document, no commit message, no PR discussion.

You're doing a code review of a codebase written by a developer who had perfect syntax knowledge, zero domain understanding, and no ability to answer follow-up questions.

At scale — tens of thousands of lines, hundreds of forms — this workflow breaks down. Engineers start cargo-culting the AI's patterns without understanding them. Bugs get fixed with patches that introduce new inconsistencies. The codebase becomes a hybrid that's harder to maintain than the original, because at least the VB6 was consistent in its conventions.

What this actually costs

Here's the math that teams don't do until it's too late.

Let's say you have a 500,000-line VB6 application and you get 70% converted through AI with a few weeks of engineering effort. Great. 350,000 lines translated. You estimate the remaining 150,000 lines will take proportionally less time because it's "less code."

But the remaining 30% is disproportionately dense. It's the error handling, the data access patterns, the COM dependencies, the state management; the hard stuff. Each line requires more investigation, more testing, more judgment than the easy 70%.

In practice, teams consistently report that the last 30% of the code consumes 70–80% of the total project effort. Your three-month estimate becomes eight months. Your two senior engineers who were supposed to rotate back to product work after the migration are still debugging conversion artifacts.

And the truly expensive part: you still don't have confidence that the converted application is behaviorally equivalent to the original. Because you haven't systematically validated it; you've spot-checked it, and every time you look closely at a new area, you find something else the AI got subtly wrong.

So what should you actually do?

If you're reading this and you're already in the middle of a DIY migration, a few practical suggestions:

Stop treating the AI output as a starting point. Treat it as a reference implementation. Read it to understand one possible approach, then make deliberate engineering decisions about the actual implementation. You'll throw away some of the generated code, and that's fine. It's faster than debugging code you don't understand.

Build a behavioral test suite before you convert. Capture the actual behavior of the legacy application (inputs, outputs, side effects, error cases) and use that as your validation baseline. This is the step everyone skips, and it's the step that would save the most time.

Categorize before you convert. Not all code is equally hard to modernize. Triage your codebase into what the AI can handle reliably (utility functions, simple CRUD, straightforward UI) and what requires human judgment (complex business logic, stateful workflows, integration points). Do the easy stuff in bulk. Plan the hard stuff as engineering work.

Don't let your best engineers do the grunt work. The people who understand the legacy system well enough to validate the conversion are also the people you need building new capabilities. If your modernization approach requires your senior engineers to spend months reviewing AI-generated code line by line, your approach has a resource allocation problem.

And if you're evaluating your options before you start: know that the gap between "AI-assisted migration" and "automated modernization" is wider than the marketing suggests. The difference isn't incremental. It's architectural. Tools that treat modernization as a translation problem will always hit the 70% wall. Platforms that understand application behavior at a deeper level, error semantics, state management, data access patterns, framework lifecycle can push through it.

The last 30% way more than a rounding error. It's where modernization actually happens.

GAPVelocity AI has modernized billions of lines of legacy code across VB6, PowerBuilder, WebForms, and other platforms. We've been doing this for over two decades through Mobilize.Net and ArtinSoft. If you're stuck in the last 30%, we should talk.

Topics:Application Modernizationlegacy modernizationgenerative AIAgentic AI

Comments

Subscribe to GAPVelocity AI Modernization Blog

FREE CODE ASSESSMENT TOOL