BLACK LABELAcademy
← Our Failures

Runtime knobs that live only in the launchd plist won't travel with the repo

intermediate5 min read · updated 2026-06-20

Market & numbers — every figure sourced

wake_threshold_before0.82 confidence_scoreest: observed value from the shipped loop.py default that silently rejected the operator's voice
wake_threshold_after0.4 confidence_scoreest: tuned value set in the supervisor plist after measuring his voice scored 0.49-0.92
voice_restart_count_per_day22 restarts/dayest: observed restart-storm count attributed to config that did not travel with the branch

Runtime knobs that live only in the launchd plist won't travel with the repo

What we tried

We tuned a voice wake-word loop to actually respond to the operator. Two knobs mattered: the wake-word confidence threshold and the microphone AGC (automatic gain control). The shipped default threshold was 0.82, which silently rejected his real voice (it scored 0.49 to 0.92). We dropped it to 0.40 and disabled AGC, because AGC inflated the quiet floor 4x and produced false wakes and 15-second captures.

The fix worked. We set both knobs in the launchd supervisor plist via the `EnvironmentVariables` dictionary (`UTAH_WAKE_THRESHOLD=0.40`, `UTAH_VOICE_AGC=0`) so the running daemon picked them up. Live voice worked end to end.

What broke

The plist is not in git. The supervisor reads its config from a `~/Library/LaunchAgents/*.plist`, and that file is part of the machine, not the repository. The application code reads those values from the environment, so the behavior was correct only as long as that one plist on that one machine held the tuned values.

The trap: the code that imports the voice loop runs from whatever branch is currently checked out. So we had two independent sources of truth pointed at the same behavior:

When someone switched branches, the code moved but the plist didn't. And worse, code-side fixes that were committed got stranded on a feature branch — so a branch switch re-introduced the pre-fix loop, which lacked the empty-wake cooldown and the high-pass filter, producing false wakes, empty commands, and roughly 22 supervisor restarts in a day. The behavior "re-broke" with no code change to the file you were looking at, because the load-bearing config never lived where you were looking.

The fix

The principle is the twelve-factor split: strict separation of config from code, where config is everything likely to vary between deploys and code does not vary. See The Twelve-Factor App, Factor III. But "separate" does not mean "untracked and invisible." The failure here was not that the threshold lived in the environment — that part is correct. The failure was that nobody could answer "which knob lives where" without grepping a plist that git never sees.

Concrete remediation, in order:

Apply it

When a fix is "set a value somewhere and it worked," ask immediately: where did the value land, and does that place travel with the repo? Three places a knob can live — code default (travels, but rigid), committed config file (travels), supervisor/plist environment (does not travel). Anything in the third bucket needs a tracked template plus a startup log line, or the next branch switch, machine rebuild, or teammate clone will silently revert behavior you thought you fixed. The litmus test from twelve-factor still applies — config out of code so the repo could go open source without leaking secrets — but add a second test of your own: could a new machine reproduce this exact behavior from the repo alone? If the answer is no, the missing piece is a knob hiding in the supervisor.

Sources

© 2026 Black Label · Education, not financial or legal advice. Every number is sourced or labeled an estimate. Subscribe for $30/month