exit 0 when accessibility is missing - #13
Open
andredezzy wants to merge 1 commit into
Open
Conversation
HotkeyMonitor.start() opens the Accessibility prompt and throws
tapCreateFailed. run() turned that into ExitCode(1), and the LaunchAgent sets
KeepAlive{SuccessfulExit: false}, so launchd relaunched the daemon, which
prompted again -- nine dialogs in a row before the grant landed.
A missing permission is not transient, so a relaunch cannot clear it. Exit 0
and let the user grant once, then start parrot again.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When Accessibility is not granted,
HotkeyMonitor.start()opens the system prompt viaAXIsProcessTrustedWithOptions([prompt: true])and throwstapCreateFailed.Parrot.run()turned that intoExitCode(1).The LaunchAgent written by
parrot install --launch-at-loginsetsKeepAlive{SuccessfulExit: false}, so launchd relaunches the daemon on that nonzero exit. The fresh process prompts again. Each cycle also reloads the model, so the loop is slow enough to look like normal startup while it stacks up dialogs.Real log from a machine where the grant went stale (the binary was rebuilt, so its code hash no longer matched the stored grant):
Nine "parrot would like to control this computer" dialogs in a row before the grant landed.
Note this is reachable without any local rebuild: since the released binary is
adhoc, linker-signed, TCC has no stable designated requirement to match, so a normal upgrade to a new release can also invalidate the existing grant and trigger the same loop.Fix
Exit 0. A missing permission is not a transient failure — relaunching cannot clear it, only a user action in System Settings can. Exiting cleanly means
KeepAlive{SuccessfulExit: false}leaves the daemon alone, the user grants once, and starts parrot again (or it starts at next login viaRunAtLoad).The printed guidance (
run \parrot setup` to configure permissions.`) is unchanged, so the user still gets told what to do — just once instead of on a loop.Precedent in the codebase
Setup.swiftalready resolves the identical situation this way — it prompts forAccessibility, tells the user what to do, and exits 0:
This change makes
runconsistent withsetuprather than introducing a newconvention.
Alternative considered
Leaving
ExitCode(1)and droppingKeepAlivefrom the generated plist instead.Rejected on two counts: the plist already exists on disk for everyone who has
run
parrot install, so a generator change would never reach them; andKeepAliveis still wanted for genuine crashes, which this does not affect —only the permission path exits 0.
Verified
Reproduced and fixed on macOS 26.4.1. Replacing the installed binary invalidates
TCC's code-hash match, which is what makes the daemon hit this path:
each cycle reloading the model first.
(
launchctl listshows- 0 com.digimata.parrot). Granting once andstarting again reaches
listening on fn holdnormally.No PR CI exists in this repo (
release.ymlruns only onv*tags), so thebranch was also built locally with
swift build -c release— clean.