The App Needed a Real Desktop, Not Just a Real Windows Machine
Before any of the debugging stories, gotchas, or architecture arguments in this series existed, there was one earlier decision that had to fail first and it's the one that actually explains why everything after it looks the way it does.
The plan that looked right on paper
The system in question wraps a Windows-only, GUI-first desktop application the kind of software that ships as a .exe, expects to be installed via a wizard, and exposes automation only through a first-party SDK that talks to the running application process. No web API, no headless mode, nothing designed for servers at all.
The instinct, reasonably, was: don't fight that. Run it inside a Windows compatibility layer, in a Linux container. Keeps everything in the same containerized, cloud-native tooling as the rest of the stack. Cheaper to host. Fits the mental model everything else in the system already used.
It did not work.
What "did not work" actually looked like
Not a clean, informative failure a slow, grinding one. The automation layer would intermittently fail to connect to the running application with low-level timeout errors, with no consistent trigger. Sometimes it worked for hours. Sometimes it failed within minutes of a restart. The compatibility layer itself would occasionally become unstable in ways that were hard to attribute to any single cause. Every fix felt like it addressed a symptom, and a slightly different symptom would show up a few days later.
Eventually the right call was: stop trying to make this specific approach reliable, and question whether the approach itself was ever going to be reliable rather than continuing to patch a foundation that might be structurally wrong.
The pivot, and the deeper thing it revealed
The fix was to abandon the compatibility-layer approach entirely and run the application on an actual Windows machine, natively. This alone mostly solved it but "mostly" is doing real work in that sentence, because it surfaced a second, more specific requirement that the first failure had been obscuring the whole time.
Running on real Windows wasn't sufficient by itself. The application's automation layer would still fail to connect if the underlying process was running in a headless or service-style context even on genuine Windows, even with no compatibility layer involved at all. It only worked reliably when the process was running inside a real, interactive, logged-in desktop session the same kind of session that exists when an actual person is sitting at the keyboard, not a background service context that happens to also be "on Windows."
This turned out to be the actual root requirement the whole time. The compatibility-layer approach hadn't just been unstable in some vague sense it was fundamentally incapable of providing this, no matter how much it was tuned, because a compatibility layer running headless in a container was never going to look like an interactive desktop session to begin with.
Why this isn't just a trading-software quirk
I think this generalizes further than it looks. A meaningful category of Windows software licensed engineering tools, certain legacy financial or back-office systems, some CAD and design applications was written assuming a human is logged in: window handles exist, GUI event loops are running, certain OS-level session facilities are available that simply don't exist in a headless context. None of that is a bug in the software. It's just an assumption baked in from an era when "run this on a server" wasn't a use case anyone designing it considered.
If you're trying to automate something in this category in the cloud, "get it running on Windows" is necessary but not sufficient. The actual question to ask early ideally before building anything around it is: does this specific piece of software's automation surface require a real interactive session, or does it genuinely work headless? That's usually one focused test, and it's a much cheaper question to answer on day one than to discover, the way this system did, after weeks of chasing intermittent failures in the wrong layer.
Once that requirement was understood clearly, the actual infrastructure need became concrete and solvable: real auto-logon configuration, launching the process specifically as an interactive session rather than a generic service, and importantly verifying which kind of session a running process is actually in as a first-class health check, not just checking whether the process exists at all. "The process is running" and "the process is running somewhere it can actually work" turned out to be two different, both necessary, checks.
What ties this back to everything else in this series
Looking back across the debugging stories, the audit findings, and the architecture arguments this series has covered, I think they're all really one lesson wearing different clothes: the constraint that looks like an inconvenient afterthought is often the actual load-bearing wall. A packaging policy decision that seems like a small CI annoyance. A "designed to fail" test that quietly carries an unrelated assumption about safety. A vault that's been silently unreachable long enough that nobody remembers assuming it worked. A scaling pattern that's right for most systems and simply wrong for this one. And here, at the root of all of it: an assumption that "a real Windows machine" and "a real interactive desktop session" were the same thing, when they were never quite the same thing at all.
None of these were exotic problems. They were all findable early, cheaply, by asking one honest, specific question before building rather than discovering the honest answer later, expensively, after something was already built on top of the wrong assumption.
Top comments (0)