2026-08-17 JAI PWA Profile Missing Root Cause
What I set out to do
Launching ~/Applications/JAI.app produced a GUI dialog: “Profile Missing — Your Firefox profile cannot be loaded. It may be missing or inaccessible.” Find out why.
What I actually did
The wording sent the first hour in the wrong direction. The profile was fine the whole time.
Reproduced the real symptom. Opening the app from Finder/Dock/Spotlight started everything correctly (loader → firefoxpwa site launch → runtime Firefox), then all three processes died together about 2.5 seconds in, silently: no stderr, no crash report, no logged denial. Running the identical loader binary from a terminal worked and stayed up. That asymmetry was the whole case.
Ruled out, one at a time: the code signature (stale, re-signed, no change), the environment (diffed the full env between both launch contexts — identical apart from OLDPWD), the working directory, __CFBundleIdentifier, and the profile directory’s permissions.
The move that cracked it was building a throwaway unsigned .app whose executable is a shell script, launching it via LaunchServices, and having it probe the filesystem from that context. First finding: it could read and write the firefoxpwa profile dir perfectly. Second, decisive finding: Firefox failed identically with --profile <valid path>, with a fresh empty profile, and with no --profile argument at all. That last one moved the fault off the profile entirely and onto the app-data root.
Root cause. The patched runtime’s application.ini carries Name=Firefox, so it resolves its app-data root to ~/Library/Application Support/Firefox — the directory the real Firefox owns, which macOS protects under App Data privacy. Firefox reads it at startup regardless of --profile. From the probe app:
--- ~/Library/Application Support/Firefox
ls: cannot open directory: Operation not permitted
--- ~/Library/Application Support/firefoxpwa
config.json firefoxpwa.log profiles
The fix was granting JAI.app Full Disk Access. Terminal launches had worked all along only because Ghostty holds FDA and children inherit it.
A second, independent breakage surfaced on the way. SDKROOT and DEVELOPER_DIR are exported into the shell (and the launchd environment) pointing at a Nix apple-sdk-14.4. firefoxpwa compiles a Swift loader per app bundle, so every system-integration update had been failing with tool 'swiftc' not found — silently, since the app stays registered in config.json and still launches from a terminal. The SillyTavern, Open WebUI, and lichess bundles were already gone from ~/Applications. Rebuilding with the nix vars stripped works, but that toolchain emits minos 28.0 on macOS 27, which makes LaunchServices reject the bundle outright with -10825; vtool fixes it, then it needs an ad-hoc re-sign because firefoxpwa leaves a stale _CodeSignature.
What was striking
The error message was actively misleading, and I let it lead. “Profile cannot be loaded” describes the symptom from Firefox’s point of view but names the wrong object — the profile was readable the entire time, and every minute spent inspecting profile permissions, lock files, and chrome patches was wasted. The test that actually resolved it was the one that removed the profile from the equation: launch with no --profile at all and watch it fail the same way.
Second thing worth keeping: the throwaway .app-wrapping-a-shell-script probe. macOS attributes file access to the responsible LaunchServices app, so no amount of testing from a terminal could ever have exposed this. Reproducing the security context, not just the command, was the entire trick.
Third: TCC grants are keyed to code signature, so my own re-signing mid-investigation would have invalidated any pre-existing grant. Repair steps can quietly destroy the evidence you’re standing on.
Top 3 next
- Regenerate the SillyTavern, Open WebUI, and lichess bundles with a clean SDK env, then
vtool+ re-sign + grant FDA to each - Decide whether to strip
SDKROOT/DEVELOPER_DIRfrom the launchd environment rather than working around them per-command - Watch whether a firefoxpwa runtime update re-breaks the signature and drops the FDA grant again
Related
- OpenRouter Interceptor — the extension lives in this PWA profile