Keep macOS from stealing your microphone.
macOS has no "preferred input device" setting. Plug in headphones with a built-in mic, join a call, wake from sleep and the system quietly switches your input away from the good microphone sitting on your desk. micpinner is a tiny background agent that puts it back.
- Pins your microphone. Anything that changes the default input away from your chosen device gets undone in about a third of a second.
- Gets out of the way when the device is gone. Unplug the pinned mic and macOS picks freely again, exactly as it normally would. Plug it back in and it is reclaimed.
- Remembers your input level. Set the gain wherever you like; it is stored and written back when the device reconnects. There is also a strict
lockmode if you want the level held against everything. - No dependencies, no polling. One Swift file talking to CoreAudio directly, event-driven, idle at ~0% CPU and ~14 MB.
- macOS 12 or later (developed and tested on macOS 26, Apple silicon)
- Xcode or the Command Line Tools (
xcode-select --install) to build
git clone https://github.com/<you>/micpinner.git
cd micpinner
./install.shinstall.sh builds the release binary, copies it to ~/.local/bin/micpinner, writes a LaunchAgent, and starts it. Set MICPINNER_BIN_DIR to install somewhere else.
Then pick a microphone:
micpinner pickThat's it. The agent starts at login from then on.
Manual install
swift build -c release
install -m 755 .build/release/micpinner ~/.local/bin/micpinner
micpinner install # writes ~/Library/LaunchAgents/com.micpinner.agent.plist and loads itmicpinner list list input devices (* = pinned)
micpinner pick list, then choose one interactively
micpinner use <n|name> pin by list number or name fragment
micpinner gain show current and remembered input level
micpinner gain 75 set the level to 75% (0-100, or 0.0-1.0)
micpinner gain remember follow your changes, restore after reconnect (default)
micpinner gain lock hold the current level, revert anything that moves it
micpinner gain off stop tracking the level entirely
micpinner status show pinned device, gain, default, agent state
micpinner install install and start the background agent
micpinner uninstall stop and remove the background agent
micpinner watch run the daemon in the foreground (used by the LaunchAgent)
Device names are matched as case-insensitive substrings, so micpinner use yeti is enough.
$ micpinner list
1 Studio Display Microphone AppleUSBAudioEngine:Apple Inc.:Studio Display:…
* 2 Yeti Stereo Microphone AppleUSBAudioEngine:Blue Microphones:Yeti… (pinned, in use)
3 Bose QC Ultra Headphones BC-87-FA-46-9A-75:input
4 MacBook Pro Microphone BuiltInMicrophoneDevice
$ micpinner status
Pinned: Yeti Stereo Microphone
Gain: remembering 74%, now 74%
Current: Yeti Stereo Microphone
Agent: loadedSome apps — conferencing tools with automatic gain control are the usual suspects — move your input level without asking. Others reset it when the device reconnects. Pick the behavior you want:
| Mode | Behavior |
|---|---|
remember (default) |
You can set the level to anything; it is remembered. It is written back only when the device reconnects or is swapped. Nothing fights you while the mic stays plugged in. |
lock |
One level, held. Anything that moves it is reverted within ~200 ms. Use it during calls with an app that keeps dragging the slider. |
off |
The level is ignored entirely. |
In remember mode a change is stored once it settles (about 3 seconds), so dragging the slider records where it lands, not every step along the way.
micpinner watch registers CoreAudio property listeners and then sleeps:
kAudioHardwarePropertyDefaultInputDevice— fires when something switches the default input.kAudioHardwarePropertyDevices— fires when the pinned device appears or disappears.kAudioDevicePropertyVolumeScalaron the pinned device — fires when the input level moves.
Notifications are coalesced (~300 ms for device changes, ~200 ms for volume) because plugging in a headset emits a burst of them and macOS may reassign the default after the first one. Corrections are compared with a small tolerance, since the volume scalar is quantized and exact float comparison would ping-pong forever.
If something else is fighting for the default input — more than five corrections within two seconds — the agent logs a warning and backs off for 30 seconds instead of spinning.
Input volume element layout differs per device: some expose a master element, others only per-channel elements (a Blue Yeti uses channels 1 and 2 with no master). The elements are discovered per device rather than assumed.
| Path | Purpose |
|---|---|
~/.local/bin/micpinner |
The binary |
~/Library/LaunchAgents/com.micpinner.agent.plist |
LaunchAgent (RunAtLoad, KeepAlive) |
~/.config/micpinner/config |
Pinned device UID + name, gain mode, remembered level |
~/Library/Logs/micpinner.log |
What the agent did, and when |
The config is plain text and safe to edit; the agent picks up changes within a couple of seconds.
uid=AppleUSBAudioEngine:Blue Microphones:Yeti Stereo Microphone:REV8:2
name=Yeti Stereo Microphone
gainmode=remember
gain=0.736
The device is matched by UID first, falling back to the name, so hardware that re-enumerates with a different UID is still recognized.
tail -f ~/Library/Logs/micpinner.log
launchctl print gui/$UID/com.micpinner.agent | grep stateA typical log:
2026-08-13T14:55:07Z pinned input present: Yeti Stereo Microphone […], gain remembered at 74%
2026-08-13T14:55:13Z restored input: Bose QC Ultra Headphones -> Yeti Stereo Microphone
2026-08-13T14:55:14Z restored gain after reconnect: 20% -> 74% (Yeti Stereo Microphone)
- Apps that select a device explicitly, instead of following the system default, are unaffected — nothing outside those apps can change that choice.
- An app's own microphone slider (Zoom's, for example) is separate from the device gain and cannot be pinned from outside. Turn off "automatically adjust microphone volume" in the app as well.
- In
remembermode, a change made by an app is also remembered — that is the cost of not locking. Usemicpinner gain lockwhen you want the level defended. - Output devices are deliberately left alone: headphones should still take over playback when you plug them in.
micpinner uninstall # stops and removes the LaunchAgent
rm ~/.local/bin/micpinner
rm -rf ~/.config/micpinner # optional: forget the pinned deviceMIT — see LICENSE.