fix(desktop): make macOS dev permission grants reliable - #1920
Conversation
|
Real-device follow-up: this patch successfully avoids the native After the dragged Electron entry is enabled in System Settings, the running Electron executable still observes Accessibility as I am converting this PR to Draft. The remaining work is to introduce a stable, validly signed development app bundle and ensure that the running executable and dragged TCC target are the same bundle. See the linked issue follow-up for the probe output and verification details. |
|
The main blocker is CI. The The E2E job is also red on A few other issues should be addressed:
The signed |
|
Thanks — addressed in
Real-device follow-up also exposed the Screen Recording restart path dropping launch arguments. The generated bundle now includes a small relaunch bootstrap and a stable isolated user-data path, so macOS “Quit & Reopen” returns to the repository app. Screen Recording onboarding now performs a real Local verification:
The unrelated project-management E2E failure should rerun on this push. |
|
Thanks for the thorough work here — the real-device validation is genuinely valuable, and the core direction is right. A dedicated, stable, signed There is one structural issue I'd address before merging, plus a few smaller ones. P1: The whole point of the relaunch bootstrap is the Screen Recording "Quit & Reopen" path, but today that path ends with a dead session:
From first principles: the dev session (Vite + env + args) and the app instance are two different lifecycles. The app may quit and restart at any time (user quit, crash, TCC reopen); the session should only end on Ctrl-C/SIGTERM. Suggested direction (not a redesign — the bundle layer is fine):
P2: environment-forwarding regression on macOS LaunchServices doesn't inherit the shell environment; only vars explicitly passed via Smaller items
Net: the architecture is right and close; fixing the session lifecycle (P1) and the env regression (P2) should be enough to make this mergeable. Happy to help verify on-device once the reopen path keeps Vite alive. 中文意见(简版)核心结论:方向正确,不需要换方案——专属签名
结论:架构正确、接近可合;修完 P1(会话生命周期)和 P2(环境转发)即可合并。 |
Done. PTAL |
|
This rework addresses the core issue well — thank you. The session-file mechanism correctly separates the app-instance lifecycle from the dev-session lifecycle, and dropping This comment updates the previous review with verification results and a few additional findings. CI is currently red on Must fix — CI is red (
P2 — env allowlist misses vars the app actually consumes A full sweep of P2 — concurrent dev across worktrees still fails silently Confirmed empirically: all worktrees share P2 (new) — a runtime rebuild wipes the active session's state, orphaning the running app
P2 (new) — The bootstrap writes P2 (new) — app boot failures are invisible to the supervisor (silent hang)
P3 (updated) — graceful shutdown Correction to the previous review: on this Electron (43.x), a process-level SIGTERM does trigger the P3 (nice-to-have)
Test coverage note The runtime-cache and policy tests are solid, but the session.json protocol (write/read/clear), Net: the architecture is right and the happy path works; closing the format check (required) plus the env allowlist, per-worktree isolation, state-survival across rebuild, and the pid/lock ordering should make this mergeable. Happy to re-verify after the next push. 中文意见(简版)这轮重构方向正确——session 机制、去
结论:架构正确、happy path 已通;收掉格式检查(必须)+ env 白名单 + 跨 worktree 隔离 + rebuild 状态保全 + pid 写入顺序即可合并。 |
The knip.json ignoreDependencies array was split across multiple lines, but the two entries fit within Biome's 100-char lineWidth, so "biome format" (the CI check-mode gate) wants them collapsed onto one line. Collapse the array to clear the format check.
…lent launch failures Several defects in the macOS dev app supervisor and runtime, all on the same session/launch lifecycle: - Write app.pid only after winning the single-instance lock. The bootstrap previously wrote it before acquiring the lock, so a losing second instance clobbered the winner's pid record and left the live app as an orphan the supervisor could no longer quit. - Isolate userData per linked git worktree. A shared profile made Chromium's single-instance lock treat a second worktree's app as a duplicate: it exited 0 while the supervisor kept serving Vite, giving "looks launched, no window". The primary checkout keeps its historical profile; linked worktrees (.git is a file) get a stable per-checkout profile. The bundle id stays com.maka.dev so TCC identity is unchanged. - Move session/pid state to a sibling dir that survives a runtime rebuild, and refuse a rebuild while a live app owns the lock. A rebuild rmSync'd the whole runtime dir, wiping the session and orphaning any running app; the state now outlives the rebuild and an occupied runtime fails early and legibly instead. - Probe for a live app after launch instead of trusting `open`'s exit code, which returns 0 the moment LaunchServices accepts the request. A crashed bootstrap now fails loudly rather than hanging the supervisor on its keep-alive timer with no window and no error. - Pin desktopDir in the runtime cache marker (schema 4 -> 5). The relaunch bootstrap bakes in an absolute path, so a moved repo must rebuild instead of loading a stale bootstrap. - Forward GH_TOKEN, GITHUB_TOKEN and RIVE_BIN through the curated dev environment (all consumed by dev tooling), and widen the SIGTERM->SIGKILL grace from 500ms to 3s so before-quit cleanup can finish.
…overlay-native-icon-crash # Conflicts: # package.json
…e-icon-crash' into fix/dev-permission-overlay-native-icon-crash # Conflicts: # apps/desktop/.gitignore # apps/desktop/scripts/dev-app-runtime.mjs # apps/desktop/scripts/dev-app-runtime.test.mjs # apps/desktop/scripts/start-dev-app.mjs
Astro-Han
left a comment
There was a problem hiding this comment.
A scoping suggestion, nothing blocking
First, the core of this PR is right. The Electron.app from npm has a broken code seal, which is the real reason both the icon crash and the TCC grants fail. Generating a stable, ad-hoc signed Maka Dev.app and launching it through LaunchServices attacks that root cause directly. Commits 1-3 are the correct fix and should stay.
The rest of the diff is a different project. The probe, the launch-status file, the session protocol, and the main.ts reporting all exist to supervise a dev process. That is a real concern, but it is not what #1919 asked for, and it is where the false-failure modes come from (quit within 30s, slow boot, the hardcoded timeout).
A much smaller shape that still fixes the issue completely:
flowchart TB
subgraph keep["keep (the actual fix)"]
A1["crash guard: skip getFileIcon unpackaged (+28)"]
A2["stable signed Maka Dev.app, cached per electron version"]
A3["launch through LaunchServices (open -a)"]
A4["screen recording consent via desktopCapturer"]
end
subgraph drop["drop or shrink"]
B1["probe + launch-status + main.ts reporting (8973abd5)"]
B2["full session protocol: env allowlist, pid lock, schema"]
end
A2 --> A3 --> A4
B1 -. "replaced by open -W" .-> A3
Three changes carry most of the weight:
open -Winstead ofopen. It blocks until the app exits, so user quit and crash both come back as a normal exit. The probe, the status file, and themain.tsready/failed reporting all disappear. The 30s timeout and its two false-failure modes cannot exist in this shape.- Let the dev bootstrap write its own pid file (one line).
main.tsgoes back to zero changes. Production startup should not know a dev harness exists. - Shrink session persistence to a single field, the Vite dev URL. That is the only thing HMR needs after a system relaunch. The env allowlist and ownership tracking are speculative until someone actually runs two dev instances at once.
Rough target: around 450-500 lines instead of 1122, with main.ts untouched. The commit history would also need a rebase to drop the two merge commits and the duplicated push.
If the supervision layer matters on its own (the worktree userData isolation and the rebuild guard in 8f3e1d1 do look useful), it reads better as a separate PR with its own rationale. It does not need to ride along with the permission fix.
One trade-off worth deciding explicitly: after open, the app's stdout/stderr no longer reach the terminal. open --stdout <file> --stderr <file> plus a tail -f in the launcher is a small compensation, or the README can point at log stream --process Electron.
中文说明
先说结论:这个 PR 的核心方向是对的。npm 里的 Electron.app 签名是破损的,这正是图标崩溃和 TCC 授权都失效的真正原因;生成一个稳定签名的 Maka Dev.app 并用 LaunchServices 启动,是在修根因。前 3 个 commit 是正确的修复,应该保留。
膨胀的部分是后面的探针、launch-status 文件、session 协议和 main.ts 上报,这些属于"开发进程监督",不是 #1919 的职责,也正是 30 秒超时、快速退出误报这些新故障的来源。
三个改动能省下大部分代码:用 open -W(等到 app 退出才返回,探针整层可以删掉)、让 bootstrap 自己写 pid(main.ts 零改动)、session 只保留 Vite URL 一个字段(重启后只有它需要恢复)。
这样大概能从 1122 行缩到 450-500 行。如果你觉得 worktree 隔离和 rebuild 保护(8f3e1d10)有价值,它们更适合单独开一个 PR,有独立的理由再评审。这只是建议,你可以按自己的判断来。
…overlay-native-icon-crash # Conflicts: # package.json
… macOS builds (#3455) app.getFileIcon(path, { size: 'large' }) is unsupported on macOS and hits a fatal NOTREACHED inside Chromium's IconLoader — the process dies with SIGTRAP before the promise settles, so loadNativeBundleIcon's try/catch never runs. Packaged builds crashed the moment the drag-to-grant permission guide loaded the app icon; dev builds were spared only by the isPackaged gate from #1920. Hoist the size choice into one shared BUNDLE_ICON_OPTIONS constant requesting 'normal' (32x32, supported everywhere), which the existing resize step upscales to the same 64x64 the code already produced. Fixes #3352 Generated-by: Claude Code

Summary
Maka Dev.appfor macOS developmentMaka Dev, not the parent terminalCloses #1919
Root cause
The npm-installed
Electron.appfailed strict bundle signature verification. Skippingapp.getFileIcon()prevented its nativeSIGTRAP, but dragging that bundle still did not produce a usable TCC grant. A valid ad-hoc signature and stable bundle ID were necessary but not sufficient when its internal executable was launched directly from a terminal: macOS continued returning Accessibilityfalseand Screen Recordingdenied.Launching the same signed bundle through LaunchServices made the TCC responsibility chain resolve to
Maka Dev; the existing permission poll then changed to granted on the real macOS device.Generated
.appcontents remain ignored and are never committed.Verification
Maka Devappeared in System Settings and Maka changed from “waiting for permission” to grantedcodesign --verify --deep --strict apps/desktop/.maka-dev/Maka\ Dev.appnpm run dev -- --user-data-dir=<isolated-dir>launched the signed bundle through LaunchServices with Vite HMR activenpm --workspace @maka/desktop run buildnpm run test:scripts(23 passed)git diff --check