← Build log

When Tech Debt Blocks Features: The Dual Audio Path That Almost Killed Auto-Recovery

January 10, 2025
TechDebtArchitecturalDecisionsRiskManagementSoloBuilderPreLaunchDecisionsAudioEngineering

How a “temporary” architectural compromise became a critical blocker—and why we’re fixing it before v1.0 launch

Published: January 10, 2025 Updated: January 11, 2025 Status: ⚠️ POSTMORTEM - Plan Changed (See update below) Tags: #TechDebt #ArchitecturalDecisions #RiskManagement #SoloBuilder #PreLaunchDecisions #AudioEngineering


⚠️ UPDATE (January 11, 2025): When the Fix Becomes the Problem

TL;DR: We implemented the single-path architecture described in this post. It broke recording completely. After hours of debugging, we reverted to the dual-path architecture. Sometimes the best decision is to fold.

What happened:

  1. ✅ Implemented single-path architecture (AVAudioEngine only)
  2. ❌ Recording stop functionality completely broken
  3. ❌ Swift process hung, didn’t respond to SIGINT
  4. ❌ Multiple audio graph configurations all failed
  5. ✅ Reverted to dual-path architecture
  6. ✅ Recording works perfectly again

The lesson: Even well-analyzed, well-planned architectural changes can fail in practice. Knowing when to revert is as important as knowing when to refactor.

Read the full story: Blog Post #130: Know When to Hold ‘Em, Know When to Fold ‘Em

Current status: Dual-path architecture restored and working. Auto-recovery feature shipped with GPL-free WAV concatenation instead of SoX.


The “So What?” - When to Fix Tech Debt vs. Ship Around It

Bottom Line Value: Learn how to identify when tech debt has crossed from “annoying” to “blocking” and make confident decisions about fixing it before launch vs. shipping with workarounds.

What You’ll Gain:

  • Framework for evaluating tech debt severity and impact
  • Real example: Dual audio path causing checkpoint recovery failures
  • Risk/reward analysis for pre-launch architectural fixes
  • Decision criteria: Fix now vs. ship and fix later
  • How to recognize “critical mass moments” in tech debt
  • ROI: Avoid shipping features that don’t work due to underlying architectural issues

Who This Helps: Solo builders and TPMs who struggle with the “fix tech debt vs. ship features” dilemma, especially in pre-launch phases.

Why Bother: Shipping a broken feature is worse than delaying launch. This framework helps you identify when tech debt has become a launch blocker and make confident decisions about fixing it.


TLDR: The Checkpoint Files Were Silent

We built a complete auto-recovery feature for crash protection. It worked perfectly in testing. Then we tested with real recordings and discovered:

  • ✅ Checkpoint files were being created every 30 seconds
  • ✅ Recovery UI appeared after crashes
  • ✅ Files were assembled and transcribed
  • ❌ The transcripts were complete gibberish

Root cause: A dual audio path architecture where two systems competed for microphone access. One got perfect audio, the other got silence.

The decision: Fix the underlying architecture before v1.0 launch, even though it means rewriting core recording code.

Why now: Because shipping a “crash recovery” feature that recovers garbage is worse than not having the feature at all.


The Problem: When “Temporary” Becomes Permanent

The Original Architecture Decision

When we built VoiceCatcher’s Swift audio engine, we made a pragmatic choice:

Use AVAudioRecorder for the main recording:

  • Simple, battle-tested API
  • Just 12 lines of code
  • Handles all file I/O automatically
  • Reliable and well-documented

Add AVAudioEngine later for real-time features:

  • Needed for silence detection (RMS calculation)
  • Needed for amplitude monitoring
  • More complex but more flexible

The assumption: “Both can use the same microphone input without conflict.”

The TODO Comment That Told the Truth

Buried in src/recorder.ts, we found this:

// TEMPORARY FIX: Disable silence detection due to AVAudioEngine/AVAudioRecorder conflict
// The Swift CLI has two competing audio systems that conflict with each other
// TODO: Fix Swift CLI to use single audio system for both recording and monitoring

Translation: We knew this was a problem. We shipped it anyway. We planned to fix it “later.”

When “Later” Became “Now”

Fast forward to implementing auto-recovery. The feature requires:

  1. Checkpoint files - Audio saved every 30 seconds during recording
  2. Recovery on crash - Assemble checkpoints and transcribe
  3. Accurate transcripts - Users need to know what they said

What we got:

User recording: "We need to discuss the Q4 budget and marketing strategy..."

Recovered transcript: "Oh, shh, shh, shh, shh, shh, shh, shh."

184 seconds of audio. 38 characters of hallucinated text.

The checkpoint files contained mostly silence because AVAudioEngine’s tap was getting starved by AVAudioRecorder’s exclusive microphone access.


The Critical Mass Moment: When Tech Debt Becomes a Blocker

The Evaluation Framework

We use this framework to decide “fix now vs. ship around it”:

1. Impact Severity

Question: How badly does this affect the feature?

  • 🟢 Minor - Feature works with small quirks
  • 🟡 Moderate - Feature works but with noticeable issues
  • 🔴 Critical - Feature fundamentally broken

Our case: 🔴 CRITICAL - Auto-recovery recovers garbage

2. Workaround Feasibility

Question: Can we ship with a workaround?

  • 🟢 Easy - Simple workaround, minimal user impact
  • 🟡 Possible - Workaround exists but adds complexity
  • 🔴 Impossible - No viable workaround

Our case: 🔴 IMPOSSIBLE - Can’t fix silent audio with software

3. User Trust Impact

Question: What happens if users encounter this?

  • 🟢 Low - Minor annoyance, users understand
  • 🟡 Medium - Frustrating but not trust-breaking
  • 🔴 High - Destroys trust in the product

Our case: 🔴 HIGH - “Crash recovery” that doesn’t work = broken promise

4. Fix Complexity

Question: How hard is it to fix properly?

  • 🟢 Simple - Few hours, low risk
  • 🟡 Moderate - Few days, manageable risk
  • 🔴 Complex - Weeks, high risk

Our case: 🟡 MODERATE - Rewrite recording path, but we have all the pieces

5. Launch Timeline Impact

Question: Does fixing this delay launch?

  • 🟢 No delay - Can fix within current timeline
  • 🟡 Minor delay - 1-2 weeks
  • 🔴 Major delay - Months

Our case: 🟢 NO DELAY - We’re pre-launch, this IS the timeline

The Decision Matrix

FactorScoreWeightWeighted Score
Impact Severity🔴 Critical5x15
Workaround Feasibility🔴 Impossible4x12
User Trust Impact🔴 High5x15
Fix Complexity🟡 Moderate2x4
Launch Timeline Impact🟢 No delay3x0
TOTAL46/60

Threshold for “fix before launch”: 35/60

Our score: 46/60 → FIX NOW


The Risk/Reward Analysis

Risks of Fixing Now

1. Implementation Bugs

  • Risk: New recording path could have bugs
  • Mitigation: Comprehensive testing, reuse existing functions
  • Probability: Medium
  • Impact: High
  • Overall: 🟡 Manageable

2. Timeline Slip

  • Risk: Could delay v1.0 launch
  • Mitigation: We’re already pre-launch, this is planned work
  • Probability: Low
  • Impact: Medium
  • Overall: 🟢 Low

3. Breaking Existing Functionality

  • Risk: Could break working recordings
  • Mitigation: Extensive testing before merge
  • Probability: Low (we have all the infrastructure)
  • Impact: High
  • Overall: 🟡 Manageable

Risks of Shipping Without Fix

1. Broken Feature

  • Risk: Auto-recovery doesn’t work
  • Probability: 100% (already confirmed)
  • Impact: Critical
  • Overall: 🔴 UNACCEPTABLE

2. User Trust Damage

  • Risk: Users lose trust in “crash recovery”
  • Probability: High
  • Impact: Critical (trust is hard to rebuild)
  • Overall: 🔴 UNACCEPTABLE

3. Support Burden

  • Risk: Users report “recovery doesn’t work”
  • Probability: High
  • Impact: High (support time, reputation damage)
  • Overall: 🔴 UNACCEPTABLE

4. Technical Debt Compounds

  • Risk: Have to fix it later anyway, but with users affected
  • Probability: 100%
  • Impact: Critical (harder to fix with users)
  • Overall: 🔴 UNACCEPTABLE

The Math

Fix now:

  • 3-5 days of work
  • Manageable risks with clear mitigation
  • Ships with working auto-recovery
  • Clean architecture for future features

Ship broken:

  • 0 days now
  • 100% chance of user-facing failures
  • Have to fix later anyway (with users affected)
  • Compounds tech debt

Decision: Fix now is the only viable option.


The Solution: Single-Path Architecture

What We’re Changing

Current (Dual-Path):

Microphone
    ├─> AVAudioRecorder ──> Main recording (perfect audio)
    └─> AVAudioEngine ──> Checkpoints (silent/broken)

New (Single-Path):

Microphone
    └─> AVAudioEngine ──> Audio buffers
                      ├─> RMS calculation (silence detection)
                      ├─> Checkpoint files (every 30s)
                      └─> Main recording (at end)

Why This Is Actually Simpler

Current complexity:

  1. AVAudioRecorder writes to temp file
  2. Read file back from disk
  3. Extract PCM data
  4. Apply normalization
  5. Return data

New simplicity:

  1. Accumulate audio buffers in memory
  2. Apply normalization
  3. Return data

Bonus: Checkpoints use the SAME audio as main recording. Guaranteed consistency.

What We’re Reusing

We already have all the infrastructure:

  • ✅ createWAVDataFromFloats() - Converts buffers to WAV
  • ✅ normalizeAudioData() - Normalizes audio levels
  • ✅ processAudioBuffer() - Processes tap buffers
  • ✅ accumulatedAudioData - Buffer accumulation pattern

We just need to:

  1. Add main recording buffer alongside checkpoint buffer
  2. Append to both in the same tap callback
  3. Write main recording at end using existing functions
  4. Remove AVAudioRecorder code

Lessons for Solo Builders and TPMs

1. “Temporary” Solutions Aren’t

The Pattern:

  • Ship quick solution to unblock feature
  • Add TODO comment to fix later
  • “Later” never comes
  • Tech debt compounds

The Fix:

  • Recognize when “temporary” is becoming permanent
  • Schedule tech debt fixes before they block features
  • Don’t ship features that depend on broken foundations

2. Critical Mass Moments Are Real

Signs you’ve hit critical mass:

  • ✅ TODO comments from months ago
  • ✅ Workarounds piling up
  • ✅ New features breaking because of old decisions
  • ✅ “We should really fix that” conversations

Action: Stop adding features. Fix the foundation.

3. Pre-Launch Is the Best Time to Fix

Why fix before v1.0:

  • No users affected by the fix
  • No support burden during transition
  • Clean slate for launch
  • Build trust from day one

Why NOT to wait:

  • Users encounter broken features
  • Support burden increases
  • Harder to fix with production data
  • Trust damage is permanent

4. Use the Decision Framework

Don’t rely on gut feel. Use structured analysis:

  1. Impact Severity - How bad is it?
  2. Workaround Feasibility - Can we ship around it?
  3. User Trust Impact - What’s the reputation cost?
  4. Fix Complexity - How hard to fix properly?
  5. Timeline Impact - Does it delay launch?

Score each factor. Set a threshold. Make the decision.


The Bottom Line

Shipping a broken feature is worse than delaying launch.

We could have shipped auto-recovery with silent checkpoint files. We could have added a disclaimer: “Recovery may not work perfectly.” We could have hoped users wouldn’t notice.

But that’s not how you build trust.

The right decision:

  • Fix the underlying architecture
  • Ship when it works properly
  • Build on a solid foundation

The wrong decision:

  • Ship broken features
  • Hope users don’t notice
  • Fix it “later” (with users affected)

What’s Next

We’re implementing the single-path architecture in the current branch. No feature flags, no half-measures. Clean implementation, comprehensive testing, ship when it’s ready.

Timeline: 3-5 days Risk: Manageable with proper testing Reward: Working auto-recovery + clean architecture for future features

Launch when ready, not when rushed.


Key Takeaways

For Solo Builders:

  • Use the decision framework to evaluate tech debt severity
  • Pre-launch is the best time to fix architectural issues
  • Don’t ship features that depend on broken foundations
  • “Temporary” solutions compound into permanent problems

For TPMs:

  • Structured risk analysis beats gut feel
  • User trust is more valuable than shipping fast
  • Technical debt has a critical mass point
  • Sometimes the right decision is to stop and fix the foundation

For Everyone:

  • Recognize critical mass moments
  • Fix before launch, not after
  • Build trust through working features
  • Clean architecture enables future velocity

This is part of our ongoing series on technical decision-making for solo builders. See also: Risk Assessment Framework and The Solo Builder’s Guide to Safe Tech Debt Cleanup.