Stop After Two Failures: Why Patches on Patches Are Killing My MEV Bot

The Night I Realize I'm Just Guessing

It's a quiet Tuesday and I'm staring at the same Solana transaction signature for the third hour in a row. The bot lands a bundle. The simulation says profit. The on-chain result says nothing — no error string I recognize, just a Custom: 6001 and a balance that doesn't move the way I expect. I do what any tired engineer does at 2 AM: I tweak a slippage constant by a tenth of a percent, redeploy, and stare at the next bundle. It fails too. I tweak another constant. It fails again.

Somewhere around attempt number five I catch myself. I'm not debugging. I'm gambling. The change-refresh-pray loop has eaten my entire evening, and I have nothing — no clearer mental model, no new data, no narrower hypothesis. Just a slightly different version of the same broken code.

I close the editor and write a sticky note for the desk: after two failures, stop. That note is the entire subject of this post. It sounds embarrassingly simple. In practice it's the hardest discipline I've tried to enforce on myself this whole project.

Why Debugging Is Hard in the First Place

There's a temptation to blame the tools. The RPC is flaky. The chain is fast. The DEX docs are incomplete. All true — but freeCodeCamp puts the deeper diagnosis bluntly: "Most debugging failures are not tool failures. They are thinking failures," and "Most bugs are not caused by bad code, but by unverified assumptions" (freeCodeCamp). I keep coming back to that second sentence. When I trace my late-night spiral honestly, every single attempted fix is based on a belief I have not actually checked.

I assume the pool is using the price I last read. I assume the simulated tip is the tip that actually lands. I assume the swap direction is the one I encode. None of these have survived an actual test against fresh data. I'm running on intuition the way a baseball hitter swings at the pitch he expects, not the one that's coming.

The academic literature backs this up in less folksy terms. A troubleshooting study on ArXiv finds that because of limited cognitive resources, programmers "generally only consider a few hypotheses of what software errors caused the failure, and usually choose an incorrect hypothesis" (ArXiv). The same paper notes that "hypotheses are poorly formed, and many rely more on intuition than information gathering." That is a precise description of me at 2 AM.

The Two-Failure Rule, Defined

Here's how I'm starting to operationalize it.

  • Attempt one — I have a hypothesis about what's wrong. I make the smallest change that would prove or disprove it. I run the test.
  • Attempt two — If attempt one fails, I update my hypothesis based strictly on the new data, not on what I wish were true. I make another small change. I run the test again.
  • Attempt three does not exist. If the second attempt also fails, I stop. I close the editor. I open a notebook. I start writing down what I actually know — facts only — versus what I am assuming.

That third "stop" is the whole rule. The reason it matters: if my second hypothesis is already wrong, the chance that a third uninformed swing lands is no better than a coin flip. Continuing to code is not debugging at that point. It's casino behavior, except the house edge is my own ego.

freeCodeCamp distills the same idea into a single line worth memorizing: "Never touch the fix until the hypothesis survives reality." Two failed fixes mean my hypothesis has not survived reality. Stop touching the code.

What "Patches on Patches" Actually Looks Like

The failure mode I'm trying to avoid isn't just wasted hours. It's the structural rot that builds when fixes accumulate. There's a great image in a Dev Community piece on structural debt — the author describes how it usually starts: "a 'temporary' global variable shows up to pass data between two modules that shouldn't have been talking directly. One hack became two. Two became a pattern" (Dev.to).

That is exactly what is happening in my bot. I have a bug where pool reserve reads are occasionally stale by one slot. Instead of figuring out why — instead of asking whether my data feed pipeline has a race condition or whether the deserialization is lagging — I add a tiny retry. The retry hides the bug for a week. Then a different symptom appears: profit estimates that should be impossible. I add a sanity-check filter. That hides the second symptom. Then the third symptom shows up: bundles landing with positive simulated profit and negative actual profit. I add a post-trade circuit breaker.

Looking at that chain now, every one of those "fixes" is a Band-Aid over the same underlying issue — a stale read at the wrong moment. The root cause never moves. The patches just relocate the symptom. The same Dev.to piece quotes a line I've taped to my monitor: "Refactoring doesn't fix structural problems — you're rearranging deck chairs on the Titanic."

Martin Fowler's well-known Technical Debt Quadrant gives a name to what I'm doing. He divides debt into four buckets along two axes — Prudent vs. Reckless, Deliberate vs. Inadvertent (Martin Fowler). My retry/filter/circuit-breaker chain is textbook Reckless-Inadvertent — I am not even aware I am incurring debt. I think I am fixing bugs.

What the Cost Actually Is

If this were a one-developer hobby project with no money on the line, the cost would be wasted nights. It isn't. Even for a solo MEV builder, every patch-on-patch costs latency, makes the code harder to reason about, and quietly raises the probability that something silently misbehaves at the worst possible moment — usually when a real opportunity finally appears.

The industry data on this is sobering. The Consortium for Information & Software Quality estimates that the cost of poor software quality in the US has reached at least $2.41 trillion, with accumulated technical debt around $1.52 trillion — what CISQ calls "the largest obstacle for organizations wanting to make changes to their existing codebases" (CISQ, November 2022).

For individual developers, the leakage is just as real. According to the Stripe Developer Coefficient, developers can spend up to 33% of their time addressing technical debt (ScioDev). And per analysis cited by Profit.co, organizations that ignore technical debt spend up to 40% more on maintenance than peers who address it early (Profit.co).

When I run a private mental projection of where my bot is heading if I keep patching instead of diagnosing, the math is depressing. The codebase moves slower every week. New DEX integrations take longer. Strange interactions appear between modules that, in a clean design, shouldn't even know about each other. The bot doesn't crash — it just rots in slow motion.

Root Cause Analysis Beats Quick Fixes — by a Lot

The alternative isn't mysterious. It's root cause analysis, which has been a standard quality-engineering discipline for decades. The Selementrix guide on RCA is blunt: "Patches address immediate issues but allow more insidious issues to remain. Permanent solutions prevent bug recurrence by fixing root causes" (Selementrix).

The payoff is measurable. PeopleSkillsForDevs estimates that RCA can reduce debugging time by 50–75% by following the Pareto Principle — 80% of problems come from 20% of causes (PeopleSkillsForDevs). Bugasura points out that defects caught at the design phase cost a fraction of those caught later: testing-phase defects cost about 15 times more than design-phase ones (Bugasura). And Selementrix cites a Five Whys case study where a team traces data-sync errors to expired database rules and gets a 50% reduction in future errors after addressing the root cause.

For an MEV bot specifically, this hits harder because the feedback loop is unforgiving. A traditional web app might let you ship a hotfix and observe over days. An on-chain trading bot competes in a permissionless arena where every misjudged transaction is real money out the door. There is no "we'll patch it in the next sprint." If the design is wrong, the design pays — every slot, every block.

The Counter-Hypothesis Habit

The part of root cause analysis I keep failing at — and slowly getting better at — is the counter-hypothesis. After I write down a diagnosis in one sentence, I force myself to ask: if this diagnosis is wrong, what else could it be?

This sounds trivial. It is brutal. Most of the time when I do this honestly, I realize I have one piece of evidence pointing at hypothesis A and zero pieces of evidence ruling out hypotheses B, C, and D. Pattern matching from past experience is doing most of the work, and pattern matching is exactly what the ArXiv troubleshooting paper warns about: novices and experts alike form a single intuition-based hypothesis and rarely evaluate it against alternatives.

A recent example. The bot reports a profit on simulation but a loss on land. My first instinct is "price moved between simulation and inclusion." Plausible. But when I force myself to write a counter-hypothesis — "maybe the simulation is using a different account state than the lander" — I realize I have no actual evidence that the price moves. I have only evidence that the result is different. The pattern "different outcome means moving market" is a story I tell because I've seen something like it before, not because today's data says it.

When I go and check, the simulation is reading from a different RPC node than the lander is sending to. They drift by a slot. The price never moves. My environment is inconsistent. A two-slot delta and a different physical machine — that's the kind of root cause you never find by patching the slippage constant.

How I'm Actually Trying to Run This Now

The practical rules I'm starting to enforce on myself:

Cap attempts at two before stopping. No third swing without a written hypothesis change. The written part is critical — typing out the new hypothesis forces me to articulate it, which is exactly the step the ArXiv paper says programmers skip.

Log before fixing, not after. When something fails the first time, I add observability before I add a fix. More logs, fuller dumps, a richer post-mortem trace. This costs five minutes and saves five hours.

Write the symptom and the root cause as separate sentences. If I can only write the symptom, I'm not ready to fix. Carlos Almonte on Medium calls this "confusing symptoms with root cause" and says it's the most common mistake in debugging (Medium / Carlos Almonte). It is mine, repeatedly.

Search my own past failures before proposing a new fix. I keep a rejected-hypotheses file. If a hypothesis already fails once with evidence, I do not get to revisit it without new evidence. This sounds obsessive. It saves me from going in circles.

Recognize patch chains and escalate to redesign. If I'm adding a third layer on top of two existing patches in the same area, the design is wrong. Not the patch. The cost of redesigning a module is high. The cost of three more months of compounding patches is much higher.

Where the Fail-Fast Principle Fits

The two-failure rule is a cousin of the older fail-fast principle: "As soon as something goes wrong, the application stops and the error message helps to detect, diagnose and correct the error" (Medium / Christian Neumanns). Fail-fast says don't swallow errors. The two-failure rule extends that to the developer: don't swallow your own confusion.

In a Solana arbitrage context, fail-fast has a sharper edge. The chain is honest. If my bundle includes a swap that violates an invariant, the runtime tells me with a custom error code and no further explanation. I can either accept that as a signal and dig into it, or I can mask it with a try-catch-style filter at the application layer and pretend everything is fine. The masking route is the express lane to a bot that quietly bleeds money. Fail-fast says let the failure surface; the two-failure rule says investigate it before patching.

What This Costs Me Emotionally

I want to be honest about the hardest part: stopping after two failures feels like quitting. Every fiber says one more change might do it. The investor brain whispers that the next attempt is the one that lands. The engineer brain knows this is exactly the same fallacy as a slot machine player insisting the next pull is the winner.

The academic framing helps. The ArXiv paper draws a distinction between entity theorists — who view ability as fixed and treat challenges as evidence of personal limits — and incremental theorists, who treat challenges as learning opportunities. Entity-theorist debugging looks like rapid patching to make the discomfort go away. Incremental-theorist debugging looks like closing the laptop, opening a notebook, and accepting that I do not yet understand what is happening.

The ego cost of stopping is real. The cost of not stopping is worse — it's the slow-motion accumulation of a codebase I can no longer reason about, and a bot I can no longer trust.

The Crypto Twist

There's a domain-specific reason this rule matters more in MEV than in most software. Traditional applications have soft failure: a slightly buggy web app produces a slightly bad user experience. An MEV bot has binary failure: each transaction either lands profitably or it doesn't, and every misbehavior shows up in an account balance that lives forever on chain. There is no staging environment for the actual auction. There is only the live arena and every other bot trying to eat your lunch.

In that environment, every patch is also a latency tax. Adding a sanity check costs microseconds. Adding three sanity checks in the hot path costs enough to be the difference between landing first and landing third. The Fowler quadrant tells us why this happens — Reckless-Deliberate debt ("we don't have time for design") and Reckless-Inadvertent debt ("what's layering?") both pile up into a system that punishes you in the one dimension you cannot afford to lose: speed.

What I'm Watching For Going Forward

I'm not pretending I've solved this. I'm pretending the opposite — that this is going to be a daily fight, that I will catch myself patching on patches more times than I'd like, and that the only way out is the same boring discipline: two attempts, then stop and think.

The specific things I'm trying to internalize, in no particular order:

  • Treat each new fix as a hypothesis, not a solution.
  • After two failed attempts, the fix isn't the problem — the hypothesis is.
  • Symptoms tell you where to look, not what to change.
  • Patches encode assumptions; assumptions accumulate; accumulations break.
  • Investigation feels slower than patching. It is faster.
  • If three patches sit on top of each other in the same module, the module needs to be redesigned, not patched a fourth time.

None of this is original. It's been written by Fowler, by the CACM authors, by Joel Spolsky, by every senior engineer who's ever inherited a codebase that looks like archaeological strata. The advice is freely available. The discipline is what's hard. The two-failure rule is my attempt to turn the advice into a habit small enough to actually obey.

Key Takeaways

  • Two failures in a row mean the hypothesis is wrong, not the patch. Stop coding. Start writing.
  • Patches encode unverified assumptions. Stack them and you build a codebase you cannot reason about.
  • Root cause analysis can reduce debugging time by 50–75% according to PeopleSkillsForDevs — the time you "save" by patching is borrowed at usurious interest.
  • Technical debt is not abstract. CISQ pegs accumulated US tech debt at roughly $1.52 trillion; Stripe finds developers spend up to 33% of their time on it.
  • In MEV specifically, every patch is also a latency tax — the design quality directly determines whether you land first or watch someone else take the trade.
  • The hardest part is the emotional discipline of stopping. Every instinct says one more try. Every instinct is wrong after the second failure.

Disclaimer

This article is for informational and educational purposes only and does not constitute financial, investment, legal, or professional advice. Content is produced independently and supported by advertising revenue. While we strive for accuracy, this article may contain unintentional errors or outdated information. Readers should independently verify all facts and data before making decisions. Company names and trademarks are referenced for analysis purposes under fair use principles. Always consult qualified professionals before making financial or legal decisions.