fix(desktop): pin OCR recognitionLanguages to process lifetime to stop SIGTRAP#7952
Conversation
…p SIGTRAP VNRecognizeTextRequest was crashing on macOS 26.3 with EXC_BREAKPOINT (SIGTRAP) on com.apple.root.utility-qos.cooperative during screenshot OCR. Crash trace shows TextRecognition enumerating an __EmptyArrayStorage via __SwiftNativeNSArrayWithContiguousStorage._objectAt(_:), tripping _assertionFailure. Root cause: the ["en-US"] array literal assigned to request.recognitionLanguages has its Swift storage tied to the call frame. Once VNImageRequestHandler.perform dispatches to Vision's background queue, the literal's storage can be released, leaving TextRecognition iterating an empty NSArray. Fix: hoist the array to a static let so the bridged NSArray backing storage lives for the lifetime of the process. Mirrors the defensive-copy treatment already applied to request.results (see BasedHardware#5891, BasedHardware#5151). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greptile SummaryPins
Confidence Score: 5/5Safe to merge — one-line call-site change backed by a well-documented process-lifetime constant, no behavior change for callers. The fix is minimal and surgical: a single assignment site is redirected to a static let that can never be freed, closing the ARC race without touching any other logic. The doc-comment precisely explains the mechanism, and the output-side equivalent (Array(observations)) was already in place. No new code paths, no data mutation, no API surface changes. No files require special attention. Important Files Changed
Sequence DiagramsequenceDiagram
participant Caller
participant RewindOCRService
participant VNRecognizeTextRequest
participant VisionQueue as Vision (utility-qos queue)
Caller->>RewindOCRService: extractTextWithBounds(from:)
RewindOCRService->>VNRecognizeTextRequest: init(completionHandler:)
RewindOCRService->>VNRecognizeTextRequest: "recognitionLanguages = Self.recognitionLanguages"
note over RewindOCRService: static let → storage lives for process lifetime
RewindOCRService->>VisionQueue: handler.perform([request])
note over VisionQueue: TextRecognition enumerates recognitionLanguages safely
VisionQueue-->>RewindOCRService: completion handler (observations)
RewindOCRService->>RewindOCRService: Array(observations) — defensive copy
RewindOCRService-->>Caller: OCRResult
Reviews (1): Last reviewed commit: "fix(desktop): pin OCR recognitionLanguag..." | Re-trigger Greptile |
|
Attaching the crash report that triggered this fix, from a user's machine running the production app: Note the call leaves The pinned 🤖 Generated with Claude Code |
Summary
EXC_BREAKPOINT (SIGTRAP)crash in the macOS desktop app triggered fromVNRecognizeTextRequestduring screenshot OCR.request.recognitionLanguages = ["en-US"]to astatic letonRewindOCRServiceso the Swift array's bridged NSArray storage lives for the lifetime of the process.Crash signature
Reported from the installed app on macOS 26.3 (
Omi Computerv0.11.463,com.omi.computer-macos):TextRecognitionenumerates what it believes is a non-empty Swift-bridgedNSArray, but the backing storage has become__EmptyArrayStorage, so_objectAt(0)trips Swift's bounds-check_assertionFailureand the process is killed with SIGTRAP. The crash is uncatchable from Swift.Root cause
In
RewindOCRService.extractTextWithBounds(from:):The
["en-US"]literal is a Swift[String]whose storage is tied to the call frame. After the closure returns andVNImageRequestHandler.perform([request])dispatches to Vision'scom.apple.root.utility-qos.cooperativequeue, the literal's storage can be deallocated. WhenTextRecognitionlater enumerates the languages array, the storage has been replaced with__EmptyArrayStorage, producing the SIGTRAP above.The output side of this same bug class is already handled defensively (see
Array(observations)with the comment referencing #5891, #5151), but the input side — therecognitionLanguagesarray — was left as a per-call literal.Fix
Pin the languages array to a process-lifetime
static let:No behavior change for callers — same languages, same order. The only difference is the storage lifetime.
Test plan
./run.shboots the dev app and a screenshot OCR cycle completes without_assertionFailurein Console.app.~/Library/Logs/DiagnosticReports/.OCRResult.blocksis still populated for screenshots containing English text.🤖 Generated with Claude Code