Know When to Hold 'Em, Know When to Fold 'Em: The Art of Reverting
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:
-
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
-
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
-
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
-
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
-
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
| Factor | Persist | Revert |
|---|---|---|
| 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:
- Works correctly ← Start here
- Works reliably ← Then this
- Works efficiently ← Then this
- 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.