Skip to content

Name the process that has the microphone - #22

Merged
TerrifiedBug merged 2 commits into
mainfrom
fix/name-the-mic-client
Sep 7, 2026
Merged

Name the process that has the microphone#22
TerrifiedBug merged 2 commits into
mainfrom
fix/name-the-mic-client

Conversation

@TerrifiedBug

Copy link
Copy Markdown
Owner

Fixes the reported bug: a recording that starts by itself gave no option to exclude the app behind it.

What was wrong

The Ignore button on the prompt and on the auto-record banner was gated on having a bundle id, and identity came only from proc_pidpath -> outermost .app. Two common capture clients have neither:

  • a system daemon has no .app on its path at all — macOS's speech daemon holds an input stream continuously when "Hey Siri" is on;
  • proc_pidpath returns 0 bytes for a process whose binary was replaced under it, which is every self-updating app's helper.

Either one produced a bare "Recording meeting" pill with no name and no Ignore, so there was nothing to click and no way to stop it recurring short of turning detection off.

A second defect made the exclusion list cosmetic even where it did match. A client we had answered about stayed the one the detector followed, and following it is a cache hit that short-circuits the scan — so a process that holds the microphone for hours hid every other app behind it, and no real call could be detected while it ran.

What changed

  • MeetingTitle.app(forPID:audioBundleID:) resolves identity from the executable path and falls back to Core Audio's own attribution (kAudioProcessPropertyBundleID, 0.02 ms, read only when prompting). The id is resolved through the installed copy and collapsed onto the outermost .app, so a helper reads as the app that owns it under the id the Settings picker would have stored, and a daemon keeps its raw identifier with its last component for a name. A bare executable stays anonymous — there is genuinely nothing to store.
  • MeetingDetector.ignoreCurrentClient() drops an excluded client out of the scan like our own pid, so the scan carries on to whatever else has the microphone. Held by pid, cleared when the microphone goes idle so config edits take effect.
  • The daemon passes a MeetingApp? through; the pill and prompt take their name and Ignore label from it.
  • README: a paragraph on capture clients that are not apps you launched.
  • Stale comment corrected with measurements.

Measured (M4, p50)

step cost
device gate 0.11 ms
cached client re-check 0.74 ms
full client scan (32 clients) 3.8 ms — the comment claimed ~45 ms
Core Audio bundle id read 0.022 ms

Nothing new runs while idle; the extra read happens only on the poll that prompts.

Verification

Before/after with harnesses compiled from the real sources, against live capture clients, holdA/holdB being bare executables that take the mic:

main:  t=  2.1s  start pid 734 -> nil "nil"          # the reported pill
       (nothing for the next 18 s — three other clients came and went, all hidden)

this:  t=  2.1s  start pid 734   -> com.apple.CoreSpeech "CoreSpeech"   [Ignore CoreSpeech]
       t=  4.1s  start pid 43465 -> com.nousresearch.hermes "Hermes"    [Ignore Hermes]
       t=  6.1s  start pid 65759 -> nil (no identity)                   [Dismiss only]
       t=  8.1s  start pid 65848 -> nil (no identity)                   [Dismiss only]

On the installed build, read out of the accessibility tree and clicked through System Events:

pill:   «Recording CoreSpeech call» «Stop from the menu bar or here»  buttons:[Ignore][Stop]
click:  CoreSpeech added to the ignore list
        stopped · 0:02 · …/2026.09.07-0913
after:  CoreSpeech is excluded — ignoring
        Hermes is excluded — ignoring
        Your microphone is in use          <- a new client still found behind both

That last line is the positive control for the second fix: with two ignored clients on the microphone, a fresh one is still detected, which main cannot do.

Local gate green: swift test, swift build -c release --arch arm64, the three smoke subcommands and the embedded __info_plist check.

Version bumped to 0.3.1 with release notes in CHANGELOG.md.

The prompt and the auto-record banner offered an Ignore button only when the
capture pid's executable sat inside an .app bundle. Two common clients don't:
a system daemon has no bundle at all, and proc_pidpath returns nothing for a
process whose binary was replaced under it, which is every self-updating app's
helper. Both produced a bare "Recording meeting" pill with no name and no way
to exclude it — measured live, macOS's speech daemon under "Hey Siri" reads as
`nil "nil"` on main.

Core Audio knows what the path doesn't. Every input stream carries a bundle id
of its own ('pbid', 0.02 ms to read), so identity now falls back to it,
resolves it through the installed copy and collapses it onto the outermost
.app: a helper reads as the app that owns it, under the same id the Settings
picker would have stored, and a daemon keeps its raw identifier with its last
component for a name. Only a bare executable is left anonymous, which is
honest — there is nothing to store.

Excluding an app then has to mean it, or the fix is cosmetic. A declined
client stays the one the detector follows, and following it is a cache hit
that skips the scan, so a process holding an input stream for hours hid every
other app behind it: after ignoring macOS's speech daemon, no real call could
be detected while Siri listened. An excluded client now drops out of the scan
like our own pid does, and the scan carries on.

Measured on an M4: device gate 0.11 ms, cached re-check 0.74 ms, full client
scan 3.8 ms at 32 clients — the comment claiming ~45 ms was stale, and is
updated. Nothing new runs while idle.
@TerrifiedBug
TerrifiedBug merged commit dd1373f into main Sep 7, 2026
1 check passed
@TerrifiedBug
TerrifiedBug deleted the fix/name-the-mic-client branch September 7, 2026 08:32
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