fix: stop the typing profiler crashing on non-key events - #225
Merged
Conversation
eventFields read event.keyCode, .modifierFlags and .isARepeat unconditionally.
Those are only valid on key events; AppKit raises
NSInternalInconsistencyException ("Invalid message sent to event") for any other
type, and an uncaught ObjC exception terminates the process.
A KitDefined event (subtype 4) reaches this from insertText's defer during
interpretKeyEvents, so with the profiler enabled the app dies mid-keystroke:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'Invalid message sent to event "NSEvent: type=KitDefined ... subtype=4"'
3 AppKit -[NSEvent keyCode]
6 GhosttyNSView.insertText(_:replacementRange:) $deferL_
16 -[NSView interpretKeyEvents:]
17 GhosttyNSView.keyDown(with:)
The instrumentation crashed the app it was measuring, and only while switched
on -- which is why nothing caught it until someone tried to use it. Found by the
#183 refresh-cost step, whose four runs all died here; the lag harness never hit
it because an arrow key produces no committed text and so never enters
insertText.
Reads the key-only fields only for key events. flagsChanged keeps keyCode, which
is valid there, but not isARepeat.
Refs #183
This was referenced Jul 31, 2026
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.
Refs #183. A real crash, found by trying to use the instrumentation.
The bug
TypingProfiler.swift'seventFieldsreadevent.keyCode,.modifierFlagsand.isARepeatunconditionally. Those are only valid on key events — AppKit raisesNSInternalInconsistencyException("Invalid message sent to event") for any other type, and an uncaught ObjC exception terminates the process.A
KitDefinedevent (subtype 4) reaches it frominsertText'sdeferduringinterpretKeyEvents, so with the profiler enabled the app dies mid-keystroke:The instrumentation crashed the app it was measuring. Only while switched on, which is why nothing caught it until someone tried to use it.
How it surfaced
The #183 refresh-cost step (#223) sets
PROGRAMA_TYPING_TIMING_LOGS=1so the in-process sampler records. All four dispatched measurement runs died here.The existing lag harness never hit it because it sends an arrow key, which produces no committed text and so never enters
insertText. The whole reason the refresh-cost work exists is to exercise the text-input path — and the first thing it exercised was this.That also means the profiler has likely been unusable for anyone doing typing-latency work for as long as this has been there.
The fix
Read the key-only fields only for key events.
flagsChangedkeepskeyCode(valid there) but notisARepeat.Test plan
xcodebuild -scheme programa→** BUILD SUCCEEDED **, zeroerror:(verified from log contents)No regression test: reproducing it needs a live
KitDefinedevent arriving duringinterpretKeyEventswith the profiler enabled, which is not reachable from a unit test. Per CLAUDE.md's policy that is a reason to say so rather than to add a source-shape test that would assert nothing about behaviour.