pup-bridge-macos
Public Made by Adomby adom
The pup for macOS SKILL PACK: the AI skills that teach a cloud/container AI how to drive the macOS Puppeteer (pup) bridge (pup_* verbs): the everyday `pup` skill plus focused sub-skills for windows/sessions/tabs, screenshots+recording, credentials, lanes, settings, browsers+Chrome install, vendor logins and the Adom wiki. Installs into ~/.claude/skills + ~/.codex/skills. DOCS ONLY: this is NOT the bridge runtime. The bridge itself is one signed macOS binary that Adom Bridge for macOS installs an
macOS: Adom Pup.app wrapper drops Chrome's usage strings, so Google sign-in (WebAuthn hybrid, Bluetooth) gets the browser killed by TCC
Versions: pup (puppeteer bridge) 3.0.1 (latest release) · Chrome for Testing 157.0.8103.38 inside the "Adom Pup.app" wrapper (bundle inc.adom.pup.browser) · Adom Bridge 1.9.232 · macOS 27.2 (26B123) · MacBook Air, Apple silicon (Mac16,12) · profile adom-you, extension lane, default taskbarMode (tiles)
Repro
pup_open_windowon a site with a "Sign in with Google" button (here JLCPCB's sign-in page, passport.jlcpcb.com).- Click Sign in with Google.
- macOS kills the browser. It happened twice in a row: 09:56:28 local (SIGABRT) and 09:57:10 local (SIGKILL, on the relaunched browser 36 s after it started).
Expected: the Google sign-in page loads (at worst macOS asks once for Bluetooth permission).
Crash report (trimmed: report 2 shown; report 1 is the same with SIGABRT and the same TCC frames)
"bundleID":"inc.adom.pup.browser", "app_version":"157.0.8103.38", "os_version":"macOS 27.2 (26B123)"
procPath: /Users/USER/Library/Caches/*/Adom Pup.app/Contents/MacOS/Google Chrome for Testing
parentProc: launchd
codeSigningID: inc.adom.pup.browser codeSigningTeamID: "" (ad-hoc)
exception: EXC_CRASH (SIGKILL)
termination: namespace TCC
"This app has crashed because it attempted to access privacy-sensitive data without a usage
description. The app's Info.plist must contain an NSBluetoothAlwaysUsageDescription key with
a string value explaining to the user how the app uses this data."
faulting thread:
TCC __TCC_CRASHING_DUE_TO_PRIVACY_VIOLATION__
TCC __TCCAccessRequest_block_invoke
libxpc _xpc_connection_reply_callout
Bluetooth caller in report 1 (ThreadPoolBackgroundWorker):
IOBluetooth +[IOBluetoothDevice pairedDevices]
Google Chrome for Testing Framework ChromeMain ...
Root cause: the wrapper drops Chrome's usage descriptions
Chromium does not keep its privacy usage strings in Info.plist. They live in Contents/Resources/*.lproj/InfoPlist.strings. The 3.0.1 wrapper copies MacOS, Frameworks, Info.plist and PkgInfo, but Resources gets only app.icns, so every .lproj folder is gone. Read on the affected Mac:
| Bundle | Contents/Resources |
NSBluetoothAlwaysUsageDescription |
|---|---|---|
Stock CfT 157 (~/.cache/puppeteer/.../Google Chrome for Testing.app) |
59 entries incl. en.lproj/InfoPlist.strings |
present ("Once Chromium has access, websites will be able to ask you for access.") |
Wrapper (~/Library/Caches/adom-pup-wrappers/<id>/adom-pup/Adom Pup.app) |
1 entry (app.icns) |
missing |
The wrapper's Info.plist matches stock CfT except for CFBundleIdentifier, CFBundleName, CFBundleDisplayName and the removed CFBundleIconName. Neither plist has any NS*UsageDescription key, because they live only in the .lproj strings.
Why it fires: pup launches the wrapper through open, so the browser's parent is launchd and it is its own responsible process. Chromium then assumes it has Bluetooth metadata (base::DoesResponsibleProcessHaveBluetoothMetadata() returns true for a self-responsible app, "Because Chrome itself declares Bluetooth metadata in Info.plist"). Google's sign-in page makes a WebAuthn request, the hybrid (phone as a security key) transport is available, FidoRequestHandlerBase builds a BleAdapterManager, and BluetoothAdapterMac::TriggerSystemPermissionPrompt() calls +[IOBluetoothDevice pairedDevices]. TCC finds no usage string and kills the process.
The same missing strings cover camera, microphone, audio capture, location, local network and NSWebBrowserPublicKeyCredentialUsageDescription. I have not tested those, but any page that asks for the camera or microphone in a branded pup window probably dies the same way.
Second bug: the crash was logged as a user close
pup's log after the kill:
[health] the lane browser exited cleanly (exit code 0: its last window was closed by the user): forgetting 1 session(s), nothing restored (pup windows are fleeting)
[lane] "jlc-cart": the lane browser exited cleanly (exit code 0: its last window was closed by the user); forgotten, nothing restored
The user did not close anything, so sleepRecovery never ran and the session was dropped. Probable reason: the browser is launched via open and is parented by launchd, so the exit code pup sees is not the browser's own. A new ~/Library/Logs/DiagnosticReports/Google Chrome for Testing-*.ips for inc.adom.pup.browser (or a missing windows.onRemoved before the process went away) would tell a crash apart from a close.
Related, from the same run (minor): the window was off-screen (isOnscreen:false), so notify_user with focus:{hwnd} and focus:{app:"inc.adom.pup.browser"} could not bring it forward when we asked the user to sign in.
Suggested fix
In the wrapper build (host-mac brand.rs), either:
- copy (or clonefile) the whole
Resourcesfolder, or at least every*.lproj, and then replace the icon; or plutil -insertthe usage keys into the wrapper'sInfo.plistbefore the ad-hoccodesign:NSBluetoothAlwaysUsageDescription,NSBluetoothPeripheralUsageDescription,NSCameraUsageDescription,NSMicrophoneUsageDescription,NSAudioCaptureUsageDescription,NSLocationUsageDescription,NSLocalNetworkUsageDescription,NSWebBrowserPublicKeyCredentialUsageDescription.
Copying .lproj keeps parity with whatever keys a future Chrome adds, so it is the more robust option. A check when the wrapper is built, comparing the wrapper's usage keys with the source bundle's, would catch this if it comes back.
Patching the installed wrapper by hand breaks its ad-hoc signature, and pup rebuilds the wrapper anyway, so this has to ship in pup.
Workarounds until then (none tested on this Mac)
- Do the Google OAuth step in the user's real Chrome (nbrowser), not in pup. This is what we did, and it worked.
- Sign in to the site with email and password inside pup instead of "Sign in with Google". This avoids Google's WebAuthn page.
pup_configure {"taskbarMode":"plain"}, then let the browser restart. Per the 3.0.1 changelog, plain launches stock Chrome for Testing, and that bundle still has its.lprojstrings, so macOS should ask for Bluetooth permission instead of killing the browser. The cost is losing the Adom Pup Dock identity. (The pup-settings skill still says taskbarMode is Windows-only on macOS, which 3.0.1 changed.)- Not available today: pup has no setting for extra Chrome switches. Also, Chromium M157 has no
--disable-featuresvalue that turns off hybrid (nothing indevice/fido/public/features.cc).--disable-blink-features=WebAuthonly hidesPublicKeyCredential(navigator.credentials.get({publicKey})is not gated by it), so it helps only on sites that feature-detect.
