Skip to content

fix(equalizer): make the privileged helper installable on a real Mac - #370

Merged
isaaclins merged 3 commits into
mainfrom
fix/signing-identity-preference
Sep 7, 2026
Merged

isaaclins merged 3 commits into
mainfrom
fix/signing-identity-preference

Conversation

@isaaclins

@isaaclins isaaclins commented Sep 3, 2026 •

Copy link
Copy Markdown
Owner

Two related things, both found by installing a signed build and actually turning the equalizer on. #367 landed the mechanism but it had never been run end to end on a machine.

Signing

LOCAL_SIGN_IDENTITY only matched Apple Development, so a machine holding a Developer ID Application certificate still fell back to the self-signed "Spotiglass Local Dev" identity, and make sign-driver had the same blind spot. Developer ID is now preferred. Verified with the real certificate installed:

codesign --verify -R '=((anchor apple generic and certificate leaf[subject.OU] = "BHAF4L4726") or certificate leaf[subject.CN] = "Spotiglass Local Dev") and identifier "com.isaaclins.spotiglass"' Spotiglass.app
  → explicit requirement satisfied

and likewise for SpotiglassEQDriver.driver against the driver requirement.

Four faults in the install path

None of these can fail in CI, because CI never registers a daemon:

  1. SMAppService reports .notFound for a daemon that has never been registered. The client treated that as fatal, so the very first enable could never install anything.
  2. The client connected to the helper immediately after register() returns, before launchd has the job up. It now waits for the service to report .enabled.
  3. The retry path unregistered an approved service and registered again, which requires the user's approval a second time. The version marker it keyed on is only written after a successful install, so every enable threw away the approval and asked again, forever. Re-registration is now limited to a genuinely stale helper on an already-enabled service.
  4. The helper was signed with com.apple.security.get-task-allow in Debug builds (Xcode injects it), and launchd will not run a Developer ID daemon that carries it: the registration succeeded and the job silently never loaded. The helper target now sets CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO.

Verified on the machine

  • signed Developer ID build in /Applications
  • registration succeeds and macOS shows its own "App Background Activity" prompt, no Terminal instructions anywhere
  • Spotiglass appears in Login Items → Allow in the Background

Still unverified

The daemon had not loaded by the end of the session, because the Background Task Management database held a stale record from earlier registrations made while the app lived in DerivedData. sfltool resetbtm was run to clear it, and Apple's own dialog says to reboot afterwards; the reboot has not happened yet. So the last two steps, the driver appearing in /Library/Audio/Plug-Ins/HAL and Spotiglass EQ showing up as an output device, still need a human on a rebooted Mac.

The picker only matched Apple Development, so a machine with a Developer ID Application certificate still fell back to the self-signed local identity. Both leaves carry the team in subject.OU, which is what the equalizer helper's requirement pins, but only Developer ID produces a build that can be handed to anyone else.
Copilot AI lite review requested due to automatic review settings September 3, 2026 17:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…real Mac

Four faults that only appear on a signed machine, none of which CI can see:

- The client treated SMAppService's .notFound as fatal. A daemon that has never been registered reports exactly that, so the first enable could never register anything.
- It connected to the helper immediately after register(), before launchd had the job up, and read the resulting failure as a broken helper.
- Its retry path unregistered an approved service and registered again, which needs the user's approval a second time. Since the version marker it keyed on is only written after a successful install, every enable threw the approval away and asked again.
- The helper was signed with com.apple.security.get-task-allow in Debug builds. launchd refuses to run a Developer ID daemon carrying it, so the job never loaded.
@isaaclins isaaclins changed the title build: prefer a Developer ID identity when signing local builds fix(equalizer): make the privileged helper installable on a real Mac Sep 3, 2026
…tly failing

Enabling the equalizer on a machine that has not allowed Spotiglass to run in
the background did nothing visible: the switch sprang back and the pane stayed
blank. launchd answers register() with EPERM in that state, the client reported
it as a generic registrationFailed, and install failures are deliberately
log-only because the operating system's own prompt is normally the whole user
experience. That rule is right for every install failure except this one, which
is a pending user decision and the only one the app cannot resolve by itself.

Missing approval is now its own case end to end. It is never retried, because
retrying cannot grant it and re-registering would discard an approval that had
already been given. The settings pane shows what happened and offers the trip
to Login Items, which is the only place it can be granted.

Also records what this cost to find: the helper is a LaunchDaemon, so macOS
enforces a launch constraint demanding the Developer ID validation category. An
Apple Development build is refused with OS_REASON_CODESIGNING after registering
successfully, and an unnotarized Developer ID build never registers at all.
There is no local-development path for the equalizer, only a notarized one.
@isaaclins
isaaclins merged commit 09bffca into main Sep 7, 2026
2 checks passed
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.

2 participants