Severe coreaudiod CPU spin (~65%) and leaked PreventUserIdleSystemSleep assertions while Hex is idle — not fixed by #139
- Dominant language
- Swift
- Stars
- 2.9k
- Forks
- 226
- PR merge metrics
- No merged PRs in 30d
Description
Related to #137 / #139. The warm-up fix in #139 addresses the ~10% idle-at-launch case. This is a worse variant that appears on an already-running Hex instance and has two distinct impacts: sustained high CPU on `coreaudiod`, and accumulated power-assertion leaks that prevent the Mac from sleeping with the lid closed.
## Environment
- macOS 26.4.1 (25E253) — Apple Silicon
- Hex 0.7.3 (build 83), installed at `/Applications/Hex.app`
- Hex process had been running for ~18 hours at time of observation, well after launch and after the last transcription
- System uptime at observation: 4d 11h
## Symptoms
1. `coreaudiod` observed pinned at **65.4% CPU** when sampled; the Hex process had been running for 18+ hours at that point. Not the 10–12% in #137.
2. When Hex was quit, `coreaudiod` dropped to **0.0% CPU instantly**. Confirms the leak is in the Hex client, consistent with the root-cause framing in #137.
3. In `pmset -g log`, a single burst release at `14:08:16` of **~2,290 audio contexts** (`com.apple.audio.context2279.preventuseridlesleep` through `context2290`), each holding both `PreventUserIdleSystemSleep` and `PreventUserIdleDisplaySleep` for exactly `01:32:25`. Net effect: the Mac cannot enter real sleep while this state is active, so closing the lid drains battery overnight.
4. Also observed during the session: `PID (Hex) Created NoDisplaySleepAssertion "Hex Voice Recording"` in the pmset log.
## What the profile shows
`sudo sample 599 5` captured while `coreaudiod` was spinning. Every hot thread in the report has the same signature — this is not a driver issue, it's client-driven traffic:
```
HALB_MIGServer_server
└─ _XObject_GetPropertyData_DAI32 (also _HasProperty, _GetPropertyData_DI32)
└─ HALS_Client::EvaluateSandboxAllowsMicAccess() + 528
└─ sandbox_check
└─ __mac_syscall
```
Supporting: heavy `HALS_ObjectMap::ReleaseObject` + `HALB_Mutex::Lock` contention across threads, suggesting many concurrent property queries against the same device gate.
Interpretation: something in the Hex process is calling `AudioObjectGetPropertyData` / `AudioObjectHasProperty` in a tight loop on a device the app has mic access to. Each query forces `coreaudiod` through a `sandbox_check` syscall to verify mic entitlement, which is where the ~65% CPU actually lands. Simultaneously, each retained audio "context" keeps sleep-prevention assertions held.
Candidates inside Hex worth auditing:
- A metering/VU polling loop that keeps running after `recorder.stop()` (the #139 fix disabled prepare-to-record warm-up at launch, but a post-transcription metering loop would be a different codepath)
- A device-change watcher polling `kAudioHardwarePropertyDevices` on a timer instead of subscribing to property-change notifications via `AudioObjectAddPropertyListener`
- Hotkey/state path that re-enumerates input devices per tick
## Why this isn't #139
- #139 removed `AVAudioRecorder.prepareToRecord()` at launch. On my machine, Hex had been running idle for 18 hours at the moment of observation — well past launch, and also past any recent transcription. The spin and assertion accumulation both grow with runtime, not at start.
- The magnitude is ~6× the #137 report (65% vs 10%), which suggests a loop frequency or concurrency difference, possibly a worker dispatched per device.
- The sleep-assertion accumulation (2,290 contexts, 1h 32m held) is not mentioned in #137 and has a separate user-visible impact (battery, lid-closed drain) distinct from the CPU symptom.
## Reproduction
I don't have a deterministic repro yet — the state accumulated in the background. Things that were true at the time:
- Hex had been left running since the previous day
- At least one transcription had been performed earlier in the session
- iOS Simulator had been run with audio at some point (unclear if related — its `iOSSimulatorAudioDevice` contexts were also in the pmset log, but those cleared on their own)
- Other HAL drivers loaded: BlackHole 2ch/16ch, MSTeamsAudioDevice, TVRemoteAudio, Apple's `ParrotAudioPlugin` (system). #137's repro didn't mention these, so they may be incidental.
Quickest diagnostic for anyone reproducing:
```
ps -o pid,%cpu,command -p "$(pgrep -x coreaudiod)"
sudo sample "$(pgrep -x coreaudiod)" 5
```
If the `sample` shows the `EvaluateSandboxAllowsMicAccess → sandbox_check → __mac_syscall` stack dominating every thread, it's this bug.
## Workaround
Quit `Hex.app`. `coreaudiod` returns to 0% CPU and the sleep assertions release. Watchdog script from #137 would also catch this, at a higher threshold.
## Suggested fix directions
- Audit any `AudioObject*` property calls issued on a timer or dispatch source; replace polling with `AudioObjectAddPropertyListenerBlock` where possible.
- On recording teardown, explicitly cancel any metering task *and* tear down any device-watch tasks spawned alongside it.
- Consider the `RecordingHelper` process-isolation path mentioned in #137 — OS-level resource release on process exit would bound the blast radius of either a polling leak or a session leak.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by auditing the RecordingHelper path, recorder teardown, and any AudioObject property calls or metering/device-watch tasks described in the report. Reproduce or diagnose with the provided ps and sample commands, then trace whether work continues while Hex is idle or after transcription. Done means idle Hex no longer drives the coreaudiod stack or accumulates sleep-prevention assertions, with the behavior verified after recording teardown.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- macos, swift
- Domain
- audio-video-rtc, desktop-dev, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100