When Architecture Refuses to Be Understood

Introduction: The Promise of Clarity

Every engineer eventually meets a system that resists being understood. Not because it’s complex in an elegant way, but because it’s complex in a historical way — a sedimentary stack of decisions, rewrites, patches, and compromises that no longer resemble a coherent architecture.

For me, that system was ConnectWise Manage.

What began as a straightforward goal — reproduce the browser’s behavior so I could automate repetitive work — turned into a months‑long excavation of a legacy product wrapped in modern UI paint. And somewhere in that excavation, I hit a moment that forced me to confront a deeper truth about architecture, automation, and the emotional cost of fighting systems that don’t want to be automated.

This is the story of that moment.


1. The Goal: Reproduce the Browser

The initial objective was simple:

Build a deterministic, scriptable client that behaves exactly like a real browser.

Not “close enough.”
Not “good enough for internal use.”
Exact.

Because ConnectWise doesn’t expose a modern UI API, the only way to automate workflows was to replicate the browser’s behavior down to the smallest detail.

This meant:

  • capturing every request
  • preserving every cookie
  • matching every redirect
  • replaying every header
  • honoring every hidden field
  • mimicking every JavaScript‑generated value

A browser isn’t just a request generator.
It’s a choreography engine.

And I needed to reproduce the dance.


2. The Authentication Flow (The Part That Actually Worked)

Here’s the twist most people don’t expect:

The authentication flow was the one part that behaved like a modern system.

It was built on Ruby on Rails — predictable, structured, and consistent. Once I mapped the sequence:

  • initial GET
  • hidden fields
  • anti‑forgery tokens
  • JavaScript mutations
  • POST
  • redirect chain
  • session cookies


I was able to reproduce it perfectly in Python.

Authentication wasn’t the problem.
Authentication was the victory.

The collapse came later.


3. The Real Failure: UI Hydration

Once authenticated, the browser loads the ConnectWise SPA (Single Page Application).
This SPA generates a set of UI context objects that the backend expects on every UI API call.

These include:

  • board context
  • ticket context
  • view state
  • screen ID
  • metadata caches
  • user parameters
  • internal navigation state

These values:

  • are created in JavaScript
  • live only in memory
  • mutate as the user navigates
  • are never exposed through a public API
  • are required for every ticket‑related endpoint

Without them, the backend cannot resolve:

  • which board you’re on
  • which filters are applied
  • which columns are visible
  • which ticket context is active
  • which UI state the user is “in”

This is the architectural truth:

The UI API is not a data API. It is a UI state machine.

And state machines cannot be automated without participating in the state transitions.


4. What Happens When You Call UI Endpoints Without UI State

4.1 Ticket details

getticket.rails
→ 500 Internal Server Error
Not because the request is wrong —
but because the backend dereferences null UI objects.

4.2 Ticket notes

getticketnotes.rails
→ 500 or empty payload
Because the ticket context was never hydrated.

4.3 Multiline board data

GetMultilineDataAction_srboard.rails
→ returns 0 rows
Not because you have no tickets —
but because the backend cannot determine which board to query.

Your session was valid.
Your cookies were correct.
Your authentication was perfect.

But the UI API refused to speak to you because you weren’t a browser.

This was the collapse.


5. Why I Don’t Use Selenium (Even Though It Would Have Worked)

Whenever you talk about reverse‑engineering a browser flow, someone inevitably asks:

“Why didn’t you just use Selenium?”

And here’s the thing:
Selenium absolutely would have worked.
It would have executed the JavaScript, hydrated the UI state, and satisfied the backend’s expectations.

But that’s exactly why I didn’t use it.

Selenium solves the surface problem while violating the architectural goal.

1. Selenium gives you a session, not understanding

My goal wasn’t “log in.”
My goal was:

  • understand the flow
  • map the architecture
  • build a deterministic client
  • eliminate the browser entirely

Selenium gives you none of that.
It gives you a robot that drives Chrome.

2. Selenium is not automation — it’s delegation

If the browser is still required, the system isn’t automated — it’s puppeteered.

3. Selenium is fragile in all the wrong ways

It introduces:

  • timing variance
  • DOM dependency
  • rendering delays
  • race conditions
  • headless quirks

It works until it doesn’t.

4. Selenium is incompatible with operator‑grade tooling

A real automation client must be:

  • portable
  • scriptable
  • testable
  • version‑controlled
  • environment‑agnostic
  • reproducible

Selenium is none of these.

5. Selenium would have defeated the purpose

Yes, it would have hydrated the UI state.
But it would have:

  • hidden the architecture
  • masked the inconsistencies
  • tied the workflow to a browser
  • created a brittle dependency
  • undermined the entire point of the project

The goal wasn’t to “get in.”
The goal was to understand why automation fails.

Selenium would have given me a session at the cost of the truth.

And the truth matters more.


6. Why I Wanted to Automate It in the First Place

A big part of this project wasn’t just about logging in.
It was about simplifying a UI that was never designed to be simple.

I had already proven this was possible with Acronis.

Acronis’ UI is clunky — painfully so — but underneath the clutter is a modern UI API. Once you peel back the layers, the architecture is clean enough to automate. You can build a CLI that’s faster, clearer, and more humane than the portal itself.

And I did.

I de‑clunkified Acronis because the architecture allowed it.

ConnectWise is the opposite.

ConnectWise is built in a world where the server is too nosy

Modern systems treat the client as the source of truth:

  • the client requests data
  • the server returns data

ConnectWise was built in a different era — one where:

  • the server tracks UI state
  • the server tracks which screen you’re on
  • the server tracks which filters you’ve applied
  • the server expects the client to simulate a WebForms page lifecycle

It’s architecture from a time when the server believed it owned the UI.

Acronis: “Tell me what you want.”

ConnectWise: “Tell me what screen you’re on.”

That’s the difference.

This is why the UI hydration failure mattered so much.
It wasn’t just about fetching tickets —
it was about unlocking the ability to build a better interface on top of a system that desperately needs one.

And when the architecture itself refused to cooperate, it wasn’t just a technical failure.
It was the collapse of the entire premise.


7. The Collapse

When the final test failed — silently, inexplicably, architecturally — something in me gave out.

Not anger.
Not frustration.

Just collapse.

I cried harder than I expected.
Not because of the endpoint.
Not because of the payload.
Because of the meaning behind it:

  • the hours invested
  • the clarity I thought I’d earned
  • the relief I thought was coming
  • the automation that would have protected me
  • the realization that the system was designed to resist me

And after the crying came the shutdown — the kind of sleep that isn’t chosen. The kind where your body says, “We’re done here.”


8. The Drift Toward Determinism

When I woke up, the first thing my mind reached for wasn’t rest.

It was the ffmpeg transcoding flow.

Not because I needed it.
Because it made sense.

Because it was deterministic.
Because it obeyed rules.
Because it didn’t fight me.
Because it didn’t require emotional labor.
Because it was a system where architecture behaves like architecture.

That’s the essence of Empirical Drift:

When one system collapses under architectural inconsistency, I drift toward one that rewards clarity.


9. The Lesson

Here’s the architectural truth at the center of all this:

You cannot automate your way out of an architecture that was designed to resist automation.

Some systems are not automatable.
Some workflows cannot be made deterministic.
Some platforms require human presence because they were built that way.
Some architectures punish clarity instead of rewarding it.

And the human cost of fighting those systems is real.


Conclusion: The Cost of Clarity

This wasn’t a story about burnout.
It was a story about architecture — and the emotional physics of working inside systems that refuse to be understood.

The ConnectWise UI API didn’t just fail technically.
It failed philosophically.

And in that failure, it revealed something important:

  • why clarity matters
  • why determinism matters
  • why architecture matters
  • why engineers drift toward systems that behave predictably
  • why the cost of inconsistency is paid in human exhaustion

Empirical Drift isn’t about feelings for their own sake.
It’s about the way technical systems shape the people who operate them.

And sometimes, the hardest truth is the simplest one:

Some systems can’t be fixed — only understood. And sometimes, understanding is the end of the road.


Appendix: Why Ticket Retrieval Fails Without a Hydrated UI Session

This appendix documents the actual architectural failure point:
ConnectWise’s UI endpoints require a fully hydrated UI session that only the SPA can produce.

The authentication flow itself — the Rails portion — was one of the only parts that behaved predictably. The collapse happened after login, when attempting to retrieve ticket data without the UI state that the SPA normally generates.


1. Authentication Did Work (Because Rails Is Predictable)

The Rails-based login flow:

  • accepted your credentials
  • issued the expected cookies
  • completed the redirect chain
  • produced a valid authenticated browser session

Your Python client reproduced this flow perfectly.

This part was not the problem.

The problem was everything that came after.


2. The UI API Requires SPA‑Generated State

Once authenticated, the browser loads the ConnectWise SPA.
This SPA generates a set of UI context objects that the backend expects on every UI API call.

These include:

  • cwClientId
  • cwViewState
  • cwScreenId
  • cwContextId
  • cwBoardContext
  • cwTicketContext
  • cwUserParams
  • cwMetadataCache

These values are:

  • created in JavaScript
  • mutated as the user navigates
  • stored in memory, not cookies
  • never exposed through a public API
  • required for all ticket‑related endpoints

Without these, the backend cannot resolve:

  • which board you’re on
  • which filters are applied
  • which columns are visible
  • which ticket context is active
  • which UI state the user is “in”

This is the architectural truth:

The UI API is not a data API. It is a UI state machine.

And state machines cannot be automated without participating in the state transitions.


3. What Happens When You Call UI Endpoints Without UI State

3.1 getticket.rails

→ 500 Internal Server Error
Because the backend dereferences null UI objects.

3.2 ticketemail.rails

→ 500 or empty payload
Because the ticket context was never hydrated.

3.3 getticketnotes.rails

→ 500
Same reason — missing UI state.

3.4 Multiline endpoints (GetMultilineDataAction_srboard.rails)

→ 0 rows
Not because you have no tickets —
but because the backend cannot determine which board to query.

Your session was valid.
Your cookies were correct.
Your authentication was perfect.

But the UI API refused to speak to you because you weren’t a browser.


4. Why This Cannot Be Reproduced Programmatically

To hydrate UI state, you would need to:

  • execute the SPA’s JavaScript
  • maintain its internal memory objects
  • simulate navigation events
  • reproduce the WebForms lifecycle
  • track board and ticket context
  • maintain the same timing and ordering as the browser

At that point, you’re not automating ConnectWise.
You’re re‑implementing ConnectWise.

And that’s the line where automation stops being automation and becomes emulation.


5. The Final Takeaway

The authentication flow was solvable.
The UI API was not.

Because the UI API is not an API —
it is a reflection of UI state, not a data interface.

And without that state, the backend collapses.

This is why the project ended where it did.
Not because you failed,
but because the architecture itself is incompatible with automation.

This article was updated on February 19, 2026