fix(hotkey): invalidate CGEventTap Mach port on teardown to stop system-wide tap leak (#488)#499
Merged
Merged
Conversation
…#488) run_event_tap tore down with CGEventTapEnable(false) + CFRelease but never called CFMachPortInvalidate, so each tap was left registered in the system's 512-entry event-tap table as a disabled orphan. Repeated create/teardown cycles -- the input-monitoring permission probe (spawned by the 15s permission monitor for Input-Monitoring-granted users) and hotkey re-registration -- accumulate orphans until the table is exhausted, after which any tap consumer including Screen Recording / screenshots fails system-wide. Declare CFMachPortInvalidate in the FFI block and call it at teardown (after CFRunLoopRemoveSource, before CFRelease). Follow-up worth doing separately: stop the passive 15s permission poll from creating a real tap at all (read state via CGPreflightListenEventAccess and reserve the active probe for explicit diagnostics) to avoid the churn even though it no longer leaks. Reported by @jmh1313. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…ath (#488) The source.is_null() error branch releases the tap that CGEventTapCreate already registered but did not invalidate it, leaking a registered orphan on that (rare) path too. Add CFMachPortInvalidate there for completeness, matching the teardown fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
silverstein
added a commit
that referenced
this pull request
Jul 17, 2026
…15s (#488) (#500) input_monitoring_runtime_issue() created and tore down a real kCGHIDEventTap on every 15s permission-monitor poll (for users who granted Input Monitoring) just to detect a "granted but unusable" state. That state is fixed for the process lifetime -- it's only cleared by restarting the app -- so re-probing is pointless churn on the system-wide input event path. Before #488's teardown fix this churn leaked taps until the tap table exhausted and keyboard/mouse froze system-wide (WindowServer could crash). Memoize the probe result so it runs exactly once per launch, preserving the diagnostic with zero ongoing churn. Complements the CFMachPortInvalidate teardown fix (#499). Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Merged
silverstein
added a commit
that referenced
this pull request
Jul 17, 2026
Patch release. Bugfixes only. - fix(hotkey): stop the system-wide CGEventTap leak — a permission probe created a kCGHIDEventTap every 15s and never invalidated it, exhausting the system tap table and freezing keyboard/mouse (and crashing WindowServer) after the app ran a few hours; quitting Minutes restored performance. Now invalidated on teardown and probed once per process. (#488, #499, #500) - fix(build): pin the Swift helper deployment targets so 0.22.0's native-call-capture helper loads on macOS 15 instead of dyld-aborting. (#494, #497) - fix(core): let `--call` auto-detect PipeWire system-audio sinks on Linux. (#489, thanks @gtheys) - fix(summarize): support the Apple engine for speaker mapping. (#487, #498) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #488 — a long-running app accumulates 512+ disabled CGEventTaps and breaks screenshots / Screen Recording system-wide.
Root cause
run_event_tap(crates/core/src/hotkey_macos.rs) tears down withCGEventTapEnable(false)+CFReleasebut never callsCFMachPortInvalidate. Disabling and releasing theCFMachPortdoes not remove aCGEventTapCreateport from the system's 512-entry event-tap table — the canonical teardown must invalidate the Mach port first. So each tap is left as a disabled orphan.The churn comes from the permission monitor: for users who have granted Input Monitoring, the 15s
spawn_permission_monitorpoll runs an active tap probe (input_monitoring_runtime_issue→probe_hotkey_monitor→CGEventTapCreate). 512 orphans × 15s ≈ 2.1h of uptime to exhaust the table, matching the report.Fix
Declare
CFMachPortInvalidatein the FFI block and call it at teardown (afterCFRunLoopRemoveSource, beforeCFRelease). Every tap — probe, dictation monitor, and each register/unregister cycle — is now actually removed, not just dereferenced. This is a complete fix for the leak with no behavior change.Recommended follow-up (separate)
The passive 15s poll should not create a real tap at all — read Input Monitoring state via the tap-free
CGPreflightListenEventAccess()and reserve the active "can I start a tap" probe for explicit diagnostics. That removes ~5,760 unnecessary tap create/teardown cycles/day (now harmless, but wasteful).Reported by @jmh1313.
🤖 Generated with Claude Code