← Build log

Know When to Hold 'Em, Know When to Fold 'Em: The Art of Reverting

January 11, 2025
SoloBuilderTechnicalDecisionsPragmatismFailureLessonsArchitecturalDecisionsRealityCheck

How we spent hours implementing a “perfect” architectural fix, only to revert it all—and why that was the right decision

Published: January 11, 2025
Tags: #SoloBuilder #TechnicalDecisions #Pragmatism #FailureLessons #ArchitecturalDecisions #RealityCheck


The “So What?” - When to Abandon Your Plan

Bottom Line Value: Learn how to recognize when a well-planned technical change isn’t working and make the hard decision to revert—before you waste days fighting reality.

What You’ll Gain:

  • Framework for deciding when to persist vs. when to revert
  • Real example: 6+ hours of debugging that ended in a full revert
  • Warning signs that your fix is making things worse
  • How to revert gracefully without losing face
  • The difference between “almost working” and “actually working”
  • ROI: Save days of debugging by recognizing dead ends early

Who This Helps: Solo builders and engineers who struggle with sunk cost fallacy, especially when a “perfect” solution isn’t working in practice.

Why Bother: Your time is finite. Knowing when to cut your losses and revert is as valuable as knowing when to refactor. Sometimes the messy solution that works beats the elegant solution that doesn’t.


TLDR: The Perfect Plan That Failed

The setup:

  • ✅ Identified tech debt (dual audio path causing silent checkpoints)
  • ✅ Analyzed the problem thoroughly
  • ✅ Designed elegant solution (single-path architecture)
  • ✅ Implemented the solution completely
  • ✅ Built successfully, no compile errors

The reality:

  • ❌ Recording stop functionality completely broken
  • ❌ Swift process hung, didn’t respond to signals
  • ❌ 6+ hours of debugging with zero progress
  • ❌ Multiple approaches all failed
  • ✅ Reverted to dual-path architecture
  • ✅ Recording works perfectly again

The lesson: Sometimes the best code you write is the code you delete.


The Story: When Analysis Meets Reality

Act 1: The Perfect Plan

We had a clear problem: checkpoint files contained mostly silence because AVAudioRecorder and AVAudioEngine competed for microphone access.

Our analysis was solid:

  • Identified root cause (dual audio path)
  • Evaluated severity (critical - auto-recovery broken)
  • Designed solution (single-path AVAudioEngine)
  • Assessed risks (moderate complexity, manageable)
  • Made the decision (fix before v1.0 launch)

See: Blog Post #129: When Tech Debt Blocks Features

The implementation went smoothly:

  • Removed AVAudioRecorder entirely
  • Consolidated to single AVAudioEngine path
  • Separate buffers for main recording and checkpoints
  • Clean code, well-structured, no compiler errors

We felt good about this.

Act 2: The First Crack

Testing the new binary:

$ ./bin/voicecatcher-capture --file /tmp/test.wav --verbose
🎙️ VoiceCatcher Audio Capture v1.0.0
[RECORDING] Starting single-path AVAudioEngine recording
[RECORDING] Audio engine started successfully

Press CTRL+C to stop…

^C
[SIGNAL] Received SIGINT - initiating graceful termination

And then… nothing.

The process hung. No output. No termination. No audio file written.

First thought: “Must be a small bug in the signal handling.”

Act 3: The Debugging Spiral

Hour 1: Added explicit SIGINT handler. Still hangs.

Hour 2: Added fflush(stderr) to all logger calls. Still hangs.

Hour 3: Tried different audio graph configurations. Still hangs.

Hour 4: Tried different tap formats. Still hangs.

Hour 5: Tried different connection patterns. Still hangs.

Hour 6: Realized even simple commands like echo "test" were hanging. System-level issue.

Hour 7: Restarted computer. Tried again. Still hangs.

Act 4: The Realization

After the restart, we tested the dual-path architecture (the “old” code we were trying to replace):

$ ./bin/voicecatcher-capture --file /tmp/test.wav --verbose
🎙️ VoiceCatcher Audio Capture v1.0.0
[RECORDING] Starting dual-path recording (AVAudioRecorder + AVAudioEngine)
[RECORDING] Both systems started successfully

Press CTRL+C to stop…

^C
[SIGNAL] Received SIGINT - stopping recording
[RECORDING] AVAudioRecorder stopped
[RECORDING] AVAudioEngine stopped
[RECORDING] Audio file written: /tmp/test.wav
✅ Recording complete

It worked perfectly.

Act 5: The Decision

The facts:

  • Single-path: 6+ hours of debugging, zero progress, completely broken
  • Dual-path: Works perfectly, proven reliable, users won’t see the “mess”

The sunk cost fallacy whispered:

  • “You’ve already spent 6 hours on this”
  • “You’re so close to figuring it out”
  • “The single-path solution is more elegant”
  • “Just one more try…”

Reality shouted:

  • “Recording doesn’t work”
  • “You have no idea why”
  • “You have a working solution right here”
  • “Ship the feature, not the architecture”

We reverted everything.


The Framework: When to Fold

The Warning Signs

🚨 You should consider reverting when:

  1. No Progress After Multiple Approaches

    • You’ve tried 3+ different solutions
    • Each attempt fails in the same way
    • You’re not learning anything new from failures
  2. The Problem Keeps Expanding

    • “Just fix this one thing” becomes “fix three more things”
    • New issues appear faster than you solve old ones
    • The scope keeps growing
  3. You Can’t Explain Why It’s Broken

    • No clear hypothesis for the failure
    • Debugging feels like random guessing
    • You’re changing things hoping something works
  4. The Old Solution Still Works

    • The “messy” code you’re replacing actually functions
    • Users aren’t complaining about the current implementation
    • The new solution doesn’t work at all
  5. Time Investment Exceeds Expected Value

    • You’ve spent 2x the estimated time
    • Still no working solution in sight
    • Other features are blocked

The Decision Matrix

FactorPersistRevert
Progress after multiple tries✅❌
Clear understanding of problem✅❌
Working alternative exists❌✅
Time investment reasonable✅❌
Users affected by current state✅❌
Learning from each attempt✅❌

Our case:

  • ❌ No progress after 6+ hours and multiple approaches
  • ❌ No clear understanding of why it’s broken
  • ✅ Dual-path architecture works perfectly
  • ❌ Time investment way over estimate
  • ❌ Users not affected (pre-launch)
  • ❌ Not learning from debugging attempts

Score: 1/6 for persist, 5/6 for revert

Decision: REVERT


How to Revert Gracefully

1. Acknowledge Reality

Don’t:

  • “I just need one more hour”
  • “I’m sure I can figure this out”
  • “The new way is better, I just need to make it work”

Do:

  • “This isn’t working and I don’t know why”
  • “I’ve spent enough time on this”
  • “The old solution works, let’s use it”

2. Document What You Learned

Even failed attempts teach you something:

  • What approaches didn’t work
  • What assumptions were wrong
  • What the real constraints are
  • Why the “messy” solution exists

We learned:

  • macOS audio APIs have subtle behaviors we don’t understand
  • AVAudioRecorder and AVAudioEngine can coexist (despite the theory)
  • The dual-path “problem” might not be a problem after all
  • Sometimes architecture that looks messy is actually battle-tested

3. Update Your Documentation

Don’t hide the failure:

  • Update the original plan with a postmortem
  • Explain why you reverted
  • Document what didn’t work
  • Save future you from trying the same thing

We updated:

  • Blog post #129 with a warning at the top
  • Technical note with full postmortem
  • This blog post to share the lesson

4. Ship the Working Solution

Don’t let perfect be the enemy of done:

  • The dual-path architecture works
  • Users don’t care about internal architecture
  • Ship the feature with the working implementation
  • Revisit the architecture later if needed

We shipped:

  • ✅ Auto-recovery feature working perfectly
  • ✅ Dual-path architecture restored
  • ✅ GPL-free WAV concatenation (the real compliance issue)
  • ✅ All tests passing

The Real Lesson: Pragmatism Over Purity

What We Got Wrong

We fell in love with the elegant solution:

  • Single audio path is “cleaner”
  • Removing code feels good
  • Consolidation seems obviously better
  • The analysis was so thorough

We forgot to ask:

  • Does it actually work?
  • Is the current solution really broken?
  • Are we solving a real problem or a theoretical one?

What We Got Right

We recognized the dead end:

  • Didn’t spend days fighting reality
  • Didn’t let sunk cost fallacy win
  • Reverted when evidence was clear
  • Shipped the working solution

The Bigger Picture

Solo builders can’t afford to:

  • Spend weeks on architectural purity
  • Fight battles with unclear victory conditions
  • Let perfect solutions block shipping
  • Ignore working code in favor of elegant code

Solo builders must:

  • Ship working features
  • Recognize dead ends quickly
  • Revert when needed
  • Focus on user value, not code aesthetics

Lessons for Solo Builders

1. Working > Elegant

The hierarchy of code quality:

  1. Works correctly ← Start here
  2. Works reliably ← Then this
  3. Works efficiently ← Then this
  4. Looks elegant ← Dead last

Don’t skip to #4 if #1 isn’t solid.

2. Sunk Cost Is Not a Reason

Bad reasoning:

  • “I’ve already spent 6 hours on this”
  • “I can’t give up now”
  • “Just one more try”

Good reasoning:

  • “Will the next hour make progress?”
  • “Do I understand the problem better now?”
  • “Is there a working alternative?”

Time already spent is irrelevant. Only future time matters.

3. Document Your Failures

Failed attempts are valuable:

  • They teach you what doesn’t work
  • They save future you from repeating mistakes
  • They help others avoid the same trap
  • They show that failure is part of the process

This blog post exists because we failed. That’s valuable.

4. Ship Features, Not Architecture

Users care about:

  • Does the feature work?
  • Is it reliable?
  • Does it solve my problem?

Users don’t care about:

  • How many audio paths you have
  • Whether your code is “elegant”
  • What your internal architecture looks like

Ship working features. Refactor later if needed.


The Bottom Line

We spent 6+ hours implementing a “perfect” architectural fix.

It didn’t work.

We reverted to the “messy” dual-path architecture.

It works perfectly.

That’s the right decision.


What’s Next

Current status:

  • ✅ Dual-path architecture restored and working
  • ✅ Auto-recovery feature shipped successfully
  • ✅ GPL compliance fixed (removed SoX, added pure Node.js WAV concatenation)
  • ✅ All tests passing
  • ✅ Ready for v1.0 launch

Future considerations:

  • Maybe revisit single-path architecture later
  • Maybe the dual-path isn’t actually a problem
  • Maybe we’ll discover why it didn’t work
  • Maybe we won’t, and that’s fine

The lesson:

Know when to hold ‘em (persist through challenges).
Know when to fold ‘em (revert when it’s not working).
Know when to walk away (ship the working solution).
Know when to run (don’t waste days on dead ends).


Key Takeaways

For Solo Builders:

  • Recognize dead ends quickly (3-5 hours max)
  • Don’t let sunk cost fallacy drive decisions
  • Working code beats elegant code every time
  • Document failures—they’re valuable lessons
  • Ship features, not architecture

For Everyone:

  • Even perfect plans can fail in practice
  • Reverting is a valid engineering decision
  • Time already spent is irrelevant
  • Focus on user value, not code purity
  • Sometimes the messy solution is the right solution

This is part of our ongoing series on pragmatic technical decision-making for solo builders. See also: When Tech Debt Blocks Features and When to Pause: The TPM Framework.