Full disclosure: troubleshooting performed with Claude, issue authored by Claude.
Describe the bug
On macOS 27 ("Golden Gate"), the OS refuses non-browser apps access to browser data directories under ~/Library/Application Support. Finicky reads those directories to resolve a configured profile name, so every read now fails with EPERM ("operation not permitted"). The profile list comes back empty, no name matches, and LaunchBrowser falls through to launching the browser with no profile argument at all — so the URL opens in whichever profile was last used.
There is no error, no dialog, and nothing in the default log. The handler looks like it simply routed to the wrong browser profile, so it reads as a config mistake. It took me a while to work out that my config was fine and the files were still there.
Confirmed on my machine for both browser families:
$ ls ~/Library/Application\ Support/Firefox
ls: /Users/…/Library/Application Support/Firefox: Operation not permitted
$ ls ~/Library/Application\ Support/Google/Chrome >/dev/null && echo readable || echo DENIED
DENIED
Note this is EPERM, not ENOENT — the files are all still present. stat() is permitted while open()/readdir() are refused, so existence tests pass and reads fail:
$ [ -f ~/Library/Application\ Support/Firefox/profiles.ini ] && echo exists
exists
$ cat ~/Library/Application\ Support/Firefox/profiles.ini
(nothing — refused)
This is the same protection that breaks Playwright's bundled Firefox on the identical directory (microsoft/playwright#42768). Related but not the same: #552 reports macOS 27.0 dropping Finicky's Automation permission for Chrome, which is a different TCC service. Nobody seems to have reported the app-data read denial yet.
Your configuration
export default {
defaultBrowser: "Firefox:Personal",
handlers: [
{ match: ["titanfile.com/*"], browser: "Firefox:Work" },
],
};
Any config using browser: "Browser:Profile" or profile: reproduces it.
To Reproduce
- On macOS 27, confirm the denial:
ls ~/Library/Application\ Support/Firefox → Operation not permitted.
- Configure a handler with a browser profile, e.g.
browser: "Firefox:Work".
- Open a matching URL.
- The URL opens in the last-used profile, not
Work. No error is shown.
- The profile dropdown in the Finicky window is empty for that browser.
- Grant Finicky Full Disk Access in System Settings → Privacy & Security, restart it, and everything works again.
Environment
Where it happens
All in apps/finicky/src/browser/launcher.go on main:
| Line |
What |
| 181 |
Chromium Local State path — read refused |
| 193 |
Firefox profiles.ini path — read refused |
| 209 |
slog.Info("Error reading profiles.ini", …) — the denial, logged below the default level |
| 230 |
slog.Warn("Could not find profile in Firefox profiles.", …) — reports an empty list as "not found" |
| 237 |
slog.Info("Error reading Local State file", …) — same, Chromium side |
| 370, 373 |
GetProfilesForBrowser — why the window's profile dropdown is empty |
And LaunchBrowser (~line 69): when resolveBrowserProfileArgs returns false, -n, --args and the profile flag are all skipped, so the browser is launched with just the URL.
Two things make it hard to notice
- A denial and a genuine absence are indistinguishable in the code — both end up as an empty profile list.
- The fallback for an unresolved profile is "launch with no profile", which is silent and plausible-looking.
Possible fixes — happy to PR any of these if they'd be useful
- Distinguish
EPERM from ENOENT and say so, with the Full Disk Access remedy, at Warn. Cheapest fix; turns a silent misroute into a one-line answer.
- Let the browser do the lookup when the list can't be read. Firefox's
-P <name> is resolved by Firefox itself — SelectStartupProfile passes it to GetProfileByName, which searches the profiles.ini Firefox reads directly. Finicky's own read only ever confirmed a lookup Firefox was about to do anyway. Passing the name through unresolved restores name-based routing for classic profiles with no permission at all. Same shape for Chromium: --profile-directory= takes the directory name and Chrome resolves it. An unknown Firefox name returns NS_ERROR_SHOW_PROFILE_MANAGER, so the worst case is a visible dialog rather than a silent misroute.
- Accept a profile directory path and pass it through, which needs no access to anything.
- Surface it in the UI — the profile dropdown could say "macOS is blocking access to this browser's profiles" instead of being empty.
Fixes 2 and 3 do not cover profiles created with Firefox's newer profile manager: their name→directory mapping exists only in the profile group store, which is inside the blocked directory, and Firefox has no command-line flag that selects one by name. Those need either the permission or an explicit path.
The proper "ask the user" route is an NSOpenPanel grant — a non-sandboxed app that has the user pick a folder gets com.apple.macl on it, persistable via a security-scoped bookmark. That avoids Full Disk Access entirely, but it's Swift work in the app shell and I haven't prototyped whether a folder selection grants recursive access. Worth noting Full Disk Access itself has no request API, so nothing can prompt for it — and because the grant is keyed to the code signature, it has to be re-added after each rebuild if you build Finicky yourself.
I have 1, 2 and 3 implemented for the Firefox path already, on top of #546. Glad to split them into a standalone PR against main instead if that's easier to review — they're independent of that PR's profile-group work.
Full disclosure: troubleshooting performed with Claude, issue authored by Claude.
Describe the bug
On macOS 27 ("Golden Gate"), the OS refuses non-browser apps access to browser data directories under
~/Library/Application Support. Finicky reads those directories to resolve a configured profile name, so every read now fails withEPERM("operation not permitted"). The profile list comes back empty, no name matches, andLaunchBrowserfalls through to launching the browser with no profile argument at all — so the URL opens in whichever profile was last used.There is no error, no dialog, and nothing in the default log. The handler looks like it simply routed to the wrong browser profile, so it reads as a config mistake. It took me a while to work out that my config was fine and the files were still there.
Confirmed on my machine for both browser families:
Note this is
EPERM, notENOENT— the files are all still present.stat()is permitted whileopen()/readdir()are refused, so existence tests pass and reads fail:This is the same protection that breaks Playwright's bundled Firefox on the identical directory (microsoft/playwright#42768). Related but not the same: #552 reports macOS 27.0 dropping Finicky's Automation permission for Chrome, which is a different TCC service. Nobody seems to have reported the app-data read denial yet.
Your configuration
Any config using
browser: "Browser:Profile"orprofile:reproduces it.To Reproduce
ls ~/Library/Application\ Support/Firefox→Operation not permitted.browser: "Firefox:Work".Work. No error is shown.Environment
Where it happens
All in
apps/finicky/src/browser/launcher.goonmain:Local Statepath — read refusedprofiles.inipath — read refusedslog.Info("Error reading profiles.ini", …)— the denial, logged below the default levelslog.Warn("Could not find profile in Firefox profiles.", …)— reports an empty list as "not found"slog.Info("Error reading Local State file", …)— same, Chromium sideGetProfilesForBrowser— why the window's profile dropdown is emptyAnd
LaunchBrowser(~line 69): whenresolveBrowserProfileArgsreturnsfalse,-n,--argsand the profile flag are all skipped, so the browser is launched with just the URL.Two things make it hard to notice
Possible fixes — happy to PR any of these if they'd be useful
EPERMfromENOENTand say so, with the Full Disk Access remedy, atWarn. Cheapest fix; turns a silent misroute into a one-line answer.-P <name>is resolved by Firefox itself —SelectStartupProfilepasses it toGetProfileByName, which searches theprofiles.iniFirefox reads directly. Finicky's own read only ever confirmed a lookup Firefox was about to do anyway. Passing the name through unresolved restores name-based routing for classic profiles with no permission at all. Same shape for Chromium:--profile-directory=takes the directory name and Chrome resolves it. An unknown Firefox name returnsNS_ERROR_SHOW_PROFILE_MANAGER, so the worst case is a visible dialog rather than a silent misroute.Fixes 2 and 3 do not cover profiles created with Firefox's newer profile manager: their name→directory mapping exists only in the profile group store, which is inside the blocked directory, and Firefox has no command-line flag that selects one by name. Those need either the permission or an explicit path.
The proper "ask the user" route is an
NSOpenPanelgrant — a non-sandboxed app that has the user pick a folder getscom.apple.maclon it, persistable via a security-scoped bookmark. That avoids Full Disk Access entirely, but it's Swift work in the app shell and I haven't prototyped whether a folder selection grants recursive access. Worth noting Full Disk Access itself has no request API, so nothing can prompt for it — and because the grant is keyed to the code signature, it has to be re-added after each rebuild if you build Finicky yourself.I have 1, 2 and 3 implemented for the Firefox path already, on top of #546. Glad to split them into a standalone PR against
maininstead if that's easier to review — they're independent of that PR's profile-group work.