Skip to content

fix(desktop): pin OCR recognitionLanguages to process lifetime to stop SIGTRAP#7952

Open
yuk1-kondo wants to merge 1 commit into
BasedHardware:mainfrom
yuk1-kondo:fix/desktop-ocr-recognition-languages-crash
Open

fix(desktop): pin OCR recognitionLanguages to process lifetime to stop SIGTRAP#7952
yuk1-kondo wants to merge 1 commit into
BasedHardware:mainfrom
yuk1-kondo:fix/desktop-ocr-recognition-languages-crash

Conversation

@yuk1-kondo

Copy link
Copy Markdown

Summary

  • Fixes a recurring EXC_BREAKPOINT (SIGTRAP) crash in the macOS desktop app triggered from VNRecognizeTextRequest during screenshot OCR.
  • Hoists request.recognitionLanguages = ["en-US"] to a static let on RewindOCRService so 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 Computer v0.11.463, com.omi.computer-macos):

Exception Type:  EXC_BREAKPOINT (SIGTRAP)
Triggered by Thread: 18, queue: com.apple.root.utility-qos.cooperative

Thread 18:
  _assertionFailure(_:_:file:line:flags:)
  closure #1 in __SwiftNativeNSArrayWithContiguousStorage._objectAt(_:)
  __EmptyArrayStorage._withVerbatimBridgedUnsafeBuffer<A>(_:)
  __ContiguousArrayStorageBase.withUnsafeBufferOfObjects<A>(_:)
  @objc __SwiftNativeNSArrayWithContiguousStorage.objectAtSubscript(_:)
  <TextRecognition.framework>
  <TextRecognition.framework>
  ...
  completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*)

TextRecognition enumerates what it believes is a non-empty Swift-bridged NSArray, but the backing storage has become __EmptyArrayStorage, so _objectAt(0) trips Swift's bounds-check _assertionFailure and the process is killed with SIGTRAP. The crash is uncatchable from Swift.

Root cause

In RewindOCRService.extractTextWithBounds(from:):

request.recognitionLanguages = ["en-US"]

The ["en-US"] literal is a Swift [String] whose storage is tied to the call frame. After the closure returns and VNImageRequestHandler.perform([request]) dispatches to Vision's com.apple.root.utility-qos.cooperative queue, the literal's storage can be deallocated. When TextRecognition later 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 — the recognitionLanguages array — was left as a per-call literal.

Fix

Pin the languages array to a process-lifetime static let:

actor RewindOCRService {
    static let shared = RewindOCRService()

    /// Recognition languages passed to VNRecognizeTextRequest.
    ///
    /// Held as a process-lifetime constant so the bridged NSArray backing storage
    /// can never be released while Vision's TextRecognition framework enumerates
    /// it asynchronously on com.apple.root.utility-qos.cooperative. ...
    private static let recognitionLanguages: [String] = ["en-US"]
    ...
}
-            request.recognitionLanguages = ["en-US"]
+            request.recognitionLanguages = Self.recognitionLanguages

No behavior change for callers — same languages, same order. The only difference is the storage lifetime.

Test plan

  • ./run.sh boots the dev app and a screenshot OCR cycle completes without _assertionFailure in Console.app.
  • Stress: capture screens for ~10 minutes with rapid content changes; no SIGTRAP in ~/Library/Logs/DiagnosticReports/.
  • Verify on macOS 14.x and macOS 26.x.
  • Spot-check that OCRResult.blocks is still populated for screenshots containing English text.

🤖 Generated with Claude Code

…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-apps

greptile-apps Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Pins VNRecognizeTextRequest.recognitionLanguages from a per-call Swift array literal to a process-lifetime static let on RewindOCRService, eliminating a Swift-to-Obj-C bridging race that caused EXC_BREAKPOINT (SIGTRAP) crashes in production.

  • Adds private static let recognitionLanguages: [String] = [\"en-US\"] and replaces the inline literal [\"en-US\"] with Self.recognitionLanguages, making the backing NSArray storage live for the duration of the process.
  • Includes a thorough doc-comment explaining the mechanism (call-frame lifetime vs. async Vision queue, __EmptyArrayStorage ARC race) and cross-references the earlier defensive copy fix for output observations (#5891, #5151).

Confidence Score: 5/5

Safe 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

Filename Overview
desktop/macos/Desktop/Sources/Rewind/Core/RewindOCRService.swift Adds a process-lifetime static constant for recognitionLanguages and substitutes it at the one call site, cleanly closing the ARC/bridging race that caused the SIGTRAP crash.

Sequence Diagram

sequenceDiagram
    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
Loading

Reviews (1): Last reviewed commit: "fix(desktop): pin OCR recognitionLanguag..." | Re-trigger Greptile

@yuk1-kondo

Copy link
Copy Markdown
Author

Attaching the crash report that triggered this fix, from a user's machine running the production app:

-------------------------------------
Translated Report (Full Report Below)
-------------------------------------
Process:             Omi Computer [58972]
Path:                /Applications/omi.app/Contents/MacOS/Omi Computer
Identifier:          com.omi.computer-macos
Version:             0.11.463 (11463)
Code Type:           ARM-64 (Native)
Role:                Background
Parent Process:      launchd [1]

Date/Time:           2026-06-14 15:08:36.3299 +0900
Launch Time:         2026-06-14 15:08:09.8255 +0900
Hardware Model:      Mac16,8
OS Version:          macOS 26.3 (25D125)
Release Type:        User

Exception Type:    EXC_BREAKPOINT (SIGTRAP)
Exception Codes:   0x0000000000000001, 0x00000001aa4cbd20
Termination Reason:  Namespace SIGNAL, Code 5, Trace/BPT trap: 5

Triggered by Thread: 18, Dispatch Queue: com.apple.root.utility-qos.cooperative

Thread 18 (triggered):
  0  libswiftCore.dylib            _assertionFailure(_:_:file:line:flags:)
  1  libswiftCore.dylib            closure #1 in __SwiftNativeNSArrayWithContiguousStorage._objectAt(_:)
  2  libswiftCore.dylib            partial apply for closure #1 in __SwiftNativeNSArrayWithContiguousStorage._objectAt(_:)
  3  libswiftCore.dylib            partial apply for closure #1 in __SwiftNativeNSArrayWithContiguousStorage._objectAt(_:)
  4  libswiftCore.dylib            __EmptyArrayStorage._withVerbatimBridgedUnsafeBuffer<A>(_:)
  5  libswiftCore.dylib            __ContiguousArrayStorageBase.withUnsafeBufferOfObjects<A>(_:)
  6  libswiftCore.dylib            @objc __SwiftNativeNSArrayWithContiguousStorage.objectAtSubscript(_:)
  7  TextRecognition               ?
  8  TextRecognition               ?
  9  TextRecognition               ?
  10 TextRecognition               ?
  11 TextRecognition               ?
  12 TextRecognition               ?
  13 TextRecognition               ?
  14 TextRecognition               ?
  15 TextRecognition               ?
  16 TextRecognition               ?
  17 libswift_Concurrency.dylib    completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*)

Note the call leaves TextRecognition (Apple's private framework under VNRecognizeTextRequest) and enters Swift via the @objc NSArray bridge. The storage backing the array being subscripted is __EmptyArrayStorage — i.e. the array has been emptied out from underneath Vision while it iterates.

The pinned static let in this PR prevents that race by giving the bridged recognitionLanguages array a process-lifetime backing storage.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant