Skip to content

⭐ Claude Code hooks silently crash on macOS 26 (TCC responsibleProcess mismatch — affects all subcommands) #3

@52216108

Description

@52216108

中文版

Bug: macOS 26 上从 Claude Code 调用任何 vibekeys 子命令都 SIGABRT — TCC responsibility-process 不匹配

环境

  • macOS 26.3.1(build 25D771280a),Apple Silicon (arm64)
  • vibekeys v0.1.5(commit de2cb59...,目前 main 分支 HEAD)
  • 通过 Claude Code 插件市场 second-state/marketplacevibekeys_app/vibekeys_plugin 安装
  • binary 部署在 /usr/local/bin/vibekeys,同时手动用 ~/Applications/VibeKeys.app 做了 .app 包装(ad-hoc 签名,Info.plist 中声明了
    NSBluetoothAlwaysUsageDescription),两个路径 cdhash 一致

现象

每次 Claude 回复后键盘屏幕同步显示功能从来不工作。一开始以为只是 hook 子命令的 bug,但深挖发现根因更普遍:

  • Terminal.app 里跑 vibekeys send "test":✅ 屏幕正常亮
  • Claude Code 子进程里跑同样的 vibekeys send "test":❌ 静默 SIGABRT(exit 134),无任何输出,经常连 .ips 崩溃报告都不会产生
  • vibekeys hook 收到任何带 hook_event_name 字段的 payload 时:❌ 同样 SIGABRT

也就是说:Claude Code 启动时所有子命令都崩,不只是 hook

根因

macOS 26 的蓝牙 TCC 授权按 (cdhash, responsibleProcess) 二元组记,不是只看 cdhash。早期崩溃报告里能看到:

EXC_CRASH SIGABRT
栈帧:
  __abort_with_payload
  abort_with_payload
  __TCC_CRASHING_DUE_TO_PRIVACY_VIOLATION__
  __TCCAccessRequest_block_invoke.229
  __tccd_send_message_block_invoke

responsibleProc : "mux0"        # Claude Code 的多路复用进程
codeSigningID   : "com.secondstate.vibekeys.cli"

Terminal.app 启动 vibekeysresponsibleProc = Terminal,首次调用会弹蓝牙授权框、用户允许后授权落地。Claude Code 直接 exec vibekeys
responsibleProc = mux0,这个二元组从未被弹框授权过,但 Claude Code 子 shell 又没有前台 UI 让弹框浮出来 —— 内核直接 kill 进程。

最小复现

# Terminal.app 里:
$ vibekeys send "from terminal"        # ✅ 屏幕亮起

# Claude Code 子进程里(任何工具调用 spawn 出来的 shell):
$ /usr/local/bin/vibekeys send "from claude code" ; echo $?
134                                     # 静默 abort
$ echo '{"hook_event_name":"Stop"}' | /usr/local/bin/vibekeys hook ; echo $?
134

验证可行的绕过方案

强制让 vibekeys 通过 .app bundle 启动,使 VibeKeys.app 自己成为 responsibleProc:

open -g -W -a /Users/<me>/Applications/VibeKeys.app --args send "<text>"   # ✅ Claude Code 子进程也能用

所以解决思路是:绝对不要在 Claude Code hook 里直接 exec vibekeys,必须走 open -W -a VibeKeys.app --args ...

上游修复建议

  1. release 里直接发 .app bundle(含 Info.plistNSBluetoothAlwaysUsageDescription),免得 macOS 26+ 的用户都要手动包装。
  2. 修改 vibekeys_plugin/hooks/hooks.json,把每个 hook command 从 vibekeys hook 改成 open -W -a /path/to/VibeKeys.app --args hook(或者由
    plugin 自带的 shim 做这层转发)。当前配置下,macOS 26 上每次 Claude Code 触发任何一个 hook 事件都会静默 abort。
  3. hook 子命令应该对未识别的事件类型、缺失的 transcript_path 做优雅降级(no-op),而不是 panic。

需要完整 .ips 崩溃报告可以私下提供。


English version

Bug: every vibekeys subcommand SIGABRTs when invoked from Claude Code on macOS 26 — TCC responsibility-process mismatch

Environment

  • macOS 26.3.1 (build 25D771280a), Apple Silicon (arm64)
  • vibekeys v0.1.5 (commit de2cb59..., the current HEAD of main)
  • Installed via Claude Code plugin marketplace second-state/marketplacevibekeys_app/vibekeys_plugin
  • Binary deployed to /usr/local/bin/vibekeys, also wrapped manually in a ~/Applications/VibeKeys.app bundle (ad-hoc signed, with Info.plist
    declaring NSBluetoothAlwaysUsageDescription) — cdhash aligned between the two paths

Symptom

On-keyboard echo (showing each Claude reply on the keyboard screen) never works. Initially looked like a bug in the hook subcommand, but the actual
root cause is more general:

  • vibekeys send "test" from Terminal.app: ✅ works perfectly
  • vibekeys send "test" from a Claude Code child process: ❌ silent SIGABRT (exit 134), no output, often no .ips generated either
  • vibekeys hook with any payload containing hook_event_name: ❌ same SIGABRT

So every subcommand crashes when launched from Claude Code, not just hook.

Root cause

macOS 26 records Bluetooth-TCC authorization keyed by (cdhash, responsibleProcess), not just cdhash. From an old crash report we have:

EXC_CRASH SIGABRT
Faulting frames:
  __abort_with_payload
  abort_with_payload
  __TCC_CRASHING_DUE_TO_PRIVACY_VIOLATION__
  __TCCAccessRequest_block_invoke.229
  __tccd_send_message_block_invoke

responsibleProc : "mux0"        # Claude Code's multiplexer
codeSigningID   : "com.secondstate.vibekeys.cli"

When Terminal.app launches vibekeys, responsibleProc = Terminal and the prompt-and-grant happens on first call. When Claude Code launches
vibekeys directly, responsibleProc = mux0, which has never been prompted for that cdhash, so the kernel kills the process before it can prompt
— there is no foreground UI from a Claude Code child shell to surface the dialog.

Minimal repro

# Terminal.app:
$ vibekeys send "from terminal"        # works, screen lights up

# Inside Claude Code (any subprocess from the CLI agent):
$ /usr/local/bin/vibekeys send "from claude code" ; echo $?
134                                     # silent abort
$ echo '{"hook_event_name":"Stop"}' | /usr/local/bin/vibekeys hook ; echo $?
134

Workaround that works

Force vibekeys to launch through the .app bundle so VibeKeys.app itself is the responsibleProc:

open -g -W -a /Users/<me>/Applications/VibeKeys.app --args send "<text>"   # ✅ works from Claude Code

So the fix is: never exec vibekeys directly from a Claude Code hook — always go through open -W -a VibeKeys.app --args ....

Suggested upstream fixes

  1. Ship the binary inside an .app bundle in releases (with Info.plist declaring NSBluetoothAlwaysUsageDescription) so users on macOS 26+
    don't have to wrap it manually.
  2. Update vibekeys_plugin/hooks/hooks.json so each hook command is open -W -a /path/to/VibeKeys.app --args hook (or have the plugin shim do
    this) instead of bare vibekeys hook. With the current config, every Claude Code session on macOS 26 silently aborts on every
    prompt/turn/notification.
  3. The hook subcommand should also degrade gracefully (no-op) on unrecognized event types and missing transcript_path, instead of panicking.

Happy to provide full .ips files privately if needed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions