Skip to content

Stop writing the Mac-wide default input per dictation; fix Bluetooth start latency and route churn - #1702

Merged
r3dbars merged 1 commit into
mainfrom
claude/bluetooth-dictation-issues-07db8c
Aug 25, 2026
Merged

Stop writing the Mac-wide default input per dictation; fix Bluetooth start latency and route churn#1702
r3dbars merged 1 commit into
mainfrom
claude/bluetooth-dictation-issues-07db8c

Conversation

@r3dbars

@r3dbars r3dbars commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Problem

Dictation over AirPods/Bluetooth was flaky: mic-start retries, fast starts falling back to a ~1.1–1.5s readiness wait, audible artifacts in music playback around dictation, and the system default input visibly flip-flopping between the AirPods and the built-in mic. A deep audit (13 parallel code sweeps + log forensics + git archaeology, then 3-lens adversarial verification of 8 hypotheses) confirmed four interacting root causes — all app-side.

Root causes → fixes

  1. Per-session Mac-wide default-input override (H1). Every Bluetooth-fallback dictation wrote kAudioHardwarePropertyDefaultInputDevice → built-in at start and restored the AirPods at stop — two machine-wide HAL writes per dictation, independent of any setting, while the per-process AUHAL bind already did all real capture work. Log ground truth: 53 writes and 29 default-input changes in one day; BT-route start median 441ms / p90 1011ms vs 86ms on built-in routes. Fix: removed the write, the restore-arming, and the orphaned temporary crash-marker plumbing. The owner-lease restore seams in ParakeetSystemInputCoordination remain as deliberate no-ops.
  2. Prewarm faked readiness on exactly the Bluetooth-fallback route (H2), publishing inputFormatReady=true with zero hardware work, so starts attempted a doomed fast path (~420ms) then paid the deterministic wait-loop floor (logged 1089–1523ms). Fix: deleted shouldDeferHardwarePrewarm/shouldDeferHardwareRecovery; prewarm and idle recovery now always do the real AUHAL bind + verified format read (safe now that this implies no Mac-wide write).
  3. The untracked writes killed the "Faster Bluetooth dictation" feature (H6). They bypassed self-write detection, so PersistentDictationInputController misread them as user mic choices and permanently relinquished ownership mid-session. Logs: 0 flaky starts in the 4h45m the feature ran; 24 in the hour after it silently died. Fix: closed by construction — dictation performs no Mac-wide writes at all now.
  4. hfp_suspected telemetry was structurally blind (H3) to fully-switched HFP (both legs ≤24kHz — confirmed live false negative) and 0-rate unsettled reads, and the settle/failure events never carried the field. Fix: widened the heuristic (unmeasured nil rates still report false) and added hfp_suspected to audioFormatContext events. Side effect (intended): silent-callback zombie resets now also trigger on fully-switched HFP routes.

Rejected after verification against real logs: macOS auto-switch as primary driver, stale-marker launch restore, HFP recovery recordings, idle-poisoning cadences.

Guardrails

  • Tests/BluetoothRouteContractTests.swift now pins the negative invariant: audioInputSnapshot must never contain a system default-input write; the retired temporary marker may not be reintroduced. New suite covers the widened HFP cases.
  • Sources/Speech/CLAUDE.md documents the invariant and its rationale.
  • scripts/dev/bluetooth-route-metrics.py compares post-fix route health (start-latency distribution, write counts, retry/fallback counts) against the captured pre-fix baseline, with a loud regression tripwire.

Verification

  • bash build.sh --no-open — clean
  • bash run-tests.sh — 12,195 passed, 0 failed
  • bash scripts/dev/agent-preflight.sh — confirms the above is the required set for this diff
  • ⚠️ Live AirPods go/no-go still pending (no CI exercises Bluetooth hardware): repeated dictation cycles with music playing — expect no default-input flips, starts well under the old 1.1–1.5s floor, no audible artifacts, and correct transcripts. Pre-recorded fallback if format negotiation fails on the AUHAL-only bind: restore-after-successful-start variant.

Net −128 lines (277+/405−): a removal-shaped fix.

🤖 Generated with Claude Code

…start latency and route churn

The 2026-08-24 Bluetooth audit confirmed four interacting root causes behind
flaky AirPods dictation (retry storms, 1.1-1.5s starts, music artifacts,
default-input flip-flop):

1. Every Bluetooth-fallback dictation wrote kAudioHardwarePropertyDefaultInputDevice
   to built-in at start and restored the AirPods at stop — two machine-wide HAL
   writes per session, independent of any setting. Capture never needed it: the
   per-process AUHAL bind (applyPreferredDictationInputDevice) does the real work.
   Removed the write, the restore arming, and the now-orphaned temporary
   crash-recovery marker plumbing. The restore/owner-lease seams in
   ParakeetSystemInputCoordination stay as permanent no-ops for any future
   temporary override.
2. Prewarm skipped all hardware work on exactly that route but still published
   inputFormatReady=true, so every start attempted a doomed fast path (~420ms)
   then paid the wait-loop floor. Deleted the deferral; prewarm/recovery now
   always do the real AUHAL bind + verified format read (safe now that idle
   prewarm no longer implies a Mac-wide write).
3. The untracked writes bypassed self-write detection, so
   PersistentDictationInputController misread them as user mic choices and
   silently relinquished ("Faster Bluetooth dictation" died mid-session; logs
   showed 0 flaky starts in 4h45m while it ran, 24 in the hour after). Closed
   by construction: dictation makes no Mac-wide writes at all now.
4. isLikelyBluetoothHandsFreeProfile could not flag fully-switched HFP (both
   legs <=24kHz) or 0-rate unsettled reads, and settle/failure events never
   carried hfp_suspected. Widened the heuristic (unmeasured nil rates still
   report false) and added the field to audioFormatContext events.

Contract tests updated accordingly, including a negative pin: audioInputSnapshot
must never contain a system default-input write again. Added
scripts/dev/bluetooth-route-metrics.py to compare post-fix route health against
the captured pre-fix baseline (BT-route median 441ms / p90 1011ms vs 86ms
built-in; 53 Mac-wide writes/day).

Verified: bash build.sh --no-open; bash run-tests.sh (12195 passed).
Remaining: live AirPods go/no-go on real hardware (no CI covers Bluetooth).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@r3dbars
r3dbars merged commit 14d3cb1 into main Aug 25, 2026
7 checks passed
@r3dbars
r3dbars deleted the claude/bluetooth-dictation-issues-07db8c branch August 25, 2026 00:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant