Repository navigation
Conversation
…recovery AirPods Max switch profile when capture starts and can change format several times. Recovery gave up after three attempts 0.9 s apart. Allow six attempts with growing waits, count them per burst of changes, and log each configuration decision with the device transport and sample rate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
The debounce period can incorrectly reset the recovery count and bypass the six-attempt burst limit.
2 open findings
What changed in this PR
Improves Bluetooth microphone recovery by extending retries, adding burst-based accounting, and expanding privacy-safe diagnostics.
Changes:
- Adds configurable recovery backoff and stability policy.
- Logs device format and recovery state.
- Adds policy tests and release notes.
| File | Description |
|---|---|
Sources/OpenWritr/AudioEngine.swift |
Integrates recovery policy and diagnostics. |
Sources/OpenWritr/MicrophoneRecoveryPolicy.swift |
Defines retry and stability behavior. |
Sources/OpenWritr/AudioDiagnostics.swift |
Adds privacy-safe format summaries. |
Tests/OpenWritrTests/MicrophoneRecoveryPolicyTests.swift |
Tests policy and diagnostic output. |
CHANGELOG.md |
Documents user-visible changes. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| return | ||
| } | ||
|
|
||
| let attempts = countedRecoveryAttemptsOnQueue() |
| ## [1.7.4] — 2026-10-13 | ||
|
|
||
| ### Fixed | ||
| - Dictation with Bluetooth headsets such as AirPods Max no longer fails after the audio engine restarts a few times while the headset switches to its microphone profile. The microphone recovery now waits longer between attempts, allows more of them, and counts them per burst of changes instead of per recording |
There was a problem hiding this comment.
No high-confidence actionable HIG defects were found.
The PR diff changes only audio recovery logic (AudioEngine.swift, AudioDiagnostics.swift, new MicrophoneRecoveryPolicy.swift). It touches no SwiftUI/AppKit views, colors, text styles, motion, or accessibility metadata. The new log line records only transport, channel count and sample rate, and never the device name or UID, so I found no privacy defect.
Rendered surfaces and variants reviewed:
- Settings: light, dark, light with larger accessibility text
- About: light, dark, dark with larger accessibility text
- Overlay, light and dark for each state: listening, transcribing, enhancing, done, copied, clipboard, enhanced-clipboard, error
I found no visible defect in these renders that this PR introduces or exposes.
Generated by Apple HIG visual review for #129 · copilot · auto · 16.8 AIC · ⌖ 0.496 AIC · ⊞ 7.8K
|
Superseded by #130 (released in v1.7.4), which replaces the AVAudioEngine capture instead of waiting longer for it to recover. The branch is left in place. |


Pull Request
Summary
With AirPods Max set as the System Default input, dictation failed on 2026-10-13. The device summary added in 1.7.3 made the cause visible in the log:
capture generation 3 failed: ... The audio engine stopped repeatedly during microphone recovery. [selection=system-default access=authorized inputs=3 default=true transport=bluetooth virtual=false channels=1 rate=24000 alive=yes busy=no]Generation 3 and 4 each ran three recovery attempts about 0.9 s apart, then gave up. A Bluetooth headset switches to its microphone profile when capture starts, which can change the format several times over a few seconds. The recovery gave up after three attempts with a fixed 250 ms wait, and the attempt count was never reset during a recording.
Changes:
MicrophoneRecoveryPolicy: up to 6 attempts, with the wait growing 250 ms → 1.5 s (about 5.25 s of settling in total, instead of about 0.75 s).Related issue
Maintainer report: "AirPods Max auf System Default und es geht nicht mehr". Follows #119 and #114 (microphone errors), which the 1.7.3 diagnostics were added for. Does not close them.
Prepared with an AI assistant using Copilot SDK in VS Code.
Validation
swift build -c release -Xswiftc -warnings-as-errors,swiftlint lint --strict, andswift testsucceed locallyHow the change was verified (the tests cover pure logic only, so say what you tried by hand for UI, audio, hotkey, or update changes):
swift build -c release -Xswiftc -warnings-as-errors: passed.swiftlint lint --strict: 0 violations.swift test: 104 tests passed, 6 new for the policy (delay growth and cap, total settling time at least 5 s, attempt limit, a stable engine starts a fresh burst, an unstable one keeps its count) and for the device format summary (never contains a name or UID).AudioEnginecaptured correctly in five scenarios run through a temporary test (deleted afterwards): cold start, three rounds with 5 s gaps, a playback stream running at 1 % volume, and start/stop chimes at the moment of capture. None triggered a configuration-change loop. The root cause of the loop is therefore not proven; this change addresses the observed symptom (too little settling time) and adds the logging needed to find the cause if it persists.Impact
Documentation