中文版
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/marketplace → vibekeys_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 启动 vibekeys 时 responsibleProc = 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 ...。
上游修复建议
- release 里直接发
.app bundle(含 Info.plist 和 NSBluetoothAlwaysUsageDescription),免得 macOS 26+ 的用户都要手动包装。
- 修改
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。
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/marketplace → vibekeys_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
- 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.
- 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.
- 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.
中文版
Bug: macOS 26 上从 Claude Code 调用任何
vibekeys子命令都 SIGABRT — TCC responsibility-process 不匹配环境
de2cb59...,目前main分支 HEAD)second-state/marketplace→vibekeys_app/vibekeys_plugin安装/usr/local/bin/vibekeys,同时手动用~/Applications/VibeKeys.app做了 .app 包装(ad-hoc 签名,Info.plist中声明了NSBluetoothAlwaysUsageDescription),两个路径 cdhash 一致现象
每次 Claude 回复后键盘屏幕同步显示功能从来不工作。一开始以为只是
hook子命令的 bug,但深挖发现根因更普遍:vibekeys send "test":✅ 屏幕正常亮vibekeys send "test":❌ 静默 SIGABRT(exit 134),无任何输出,经常连.ips崩溃报告都不会产生vibekeys hook收到任何带hook_event_name字段的 payload 时:❌ 同样 SIGABRT也就是说:Claude Code 启动时所有子命令都崩,不只是
hook。根因
macOS 26 的蓝牙 TCC 授权按 (cdhash, responsibleProcess) 二元组记,不是只看 cdhash。早期崩溃报告里能看到:
Terminal.app 启动
vibekeys时responsibleProc = Terminal,首次调用会弹蓝牙授权框、用户允许后授权落地。Claude Code 直接 execvibekeys时responsibleProc = mux0,这个二元组从未被弹框授权过,但 Claude Code 子 shell 又没有前台 UI 让弹框浮出来 —— 内核直接 kill 进程。最小复现
验证可行的绕过方案
强制让 vibekeys 通过
.appbundle 启动,使 VibeKeys.app 自己成为 responsibleProc:所以解决思路是:绝对不要在 Claude Code hook 里直接 exec
vibekeys,必须走open -W -a VibeKeys.app --args ...。上游修复建议
.appbundle(含Info.plist和NSBluetoothAlwaysUsageDescription),免得 macOS 26+ 的用户都要手动包装。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。
hook子命令应该对未识别的事件类型、缺失的transcript_path做优雅降级(no-op),而不是 panic。需要完整
.ips崩溃报告可以私下提供。English version
Bug: every
vibekeyssubcommand SIGABRTs when invoked from Claude Code on macOS 26 — TCC responsibility-process mismatchEnvironment
de2cb59..., the current HEAD ofmain)second-state/marketplace→vibekeys_app/vibekeys_plugin/usr/local/bin/vibekeys, also wrapped manually in a~/Applications/VibeKeys.appbundle (ad-hoc signed, withInfo.plistdeclaring
NSBluetoothAlwaysUsageDescription) — cdhash aligned between the two pathsSymptom
On-keyboard echo (showing each Claude reply on the keyboard screen) never works. Initially looked like a bug in the
hooksubcommand, but the actualroot cause is more general:
vibekeys send "test"from Terminal.app: ✅ works perfectlyvibekeys send "test"from a Claude Code child process: ❌ silent SIGABRT (exit 134), no output, often no.ipsgenerated eithervibekeys hookwith any payload containinghook_event_name: ❌ same SIGABRTSo 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:
When Terminal.app launches
vibekeys,responsibleProc = Terminaland the prompt-and-grant happens on first call. When Claude Code launchesvibekeysdirectly,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
Workaround that works
Force vibekeys to launch through the
.appbundle so VibeKeys.app itself is the responsibleProc:So the fix is: never
exec vibekeysdirectly from a Claude Code hook — always go throughopen -W -a VibeKeys.app --args ....Suggested upstream fixes
.appbundle in releases (withInfo.plistdeclaringNSBluetoothAlwaysUsageDescription) so users on macOS 26+don't have to wrap it manually.
vibekeys_plugin/hooks/hooks.jsonso each hook command isopen -W -a /path/to/VibeKeys.app --args hook(or have the plugin shim dothis) instead of bare
vibekeys hook. With the current config, every Claude Code session on macOS 26 silently aborts on everyprompt/turn/notification.
hooksubcommand should also degrade gracefully (no-op) on unrecognized event types and missingtranscript_path, instead of panicking.Happy to provide full
.ipsfiles privately if needed.