Download Latest Version v0.4.0 -- QAT Gemma 4 default models source code.tar.gz (5.4 MB)
Email in envelope

Get an email when there's a new version of AgentHandover

Home / v0.2.8
Name Modified Size InfoDownloads / Week
Parent folder
AgentHandover-0.2.8.pkg 2026-04-15 10.5 MB
README.md 2026-04-14 2.8 kB
v0.2.8 -- OCR timeout abort fix + diagnostic infrastructure source code.tar.gz 2026-04-14 5.3 MB
v0.2.8 -- OCR timeout abort fix + diagnostic infrastructure source code.zip 2026-04-14 5.5 MB
Totals: 4 Items   21.4 MB 0

Fixes a silent daemon abort caused by a Tokio-level OCR timeout that orphaned an in-flight Vision framework call. Reported by hikoae with a precise diagnostic showing OCR timed out after 500ms as the last log line before silent process death.

Root cause

The OCR wrapper in crates/daemon/src/platform/macos_ocr.rs used tokio::time::timeout(500ms, spawn_blocking(perform_ocr_safe)). When the timeout fired, Tokio dropped the future but the blocking thread kept running the Vision framework call. When Vision eventually finished, ObjC cleanup (request deallocation, autoreleasepool drain) ran outside the @try/@catch/@autoreleasepool scope in perform_ocr_safe() — the Tokio future that owned the context had moved on.

The uncaught ObjC exception triggered abort() → SIGABRT with default handler → immediate process termination, no signal handlers fire, no shutdown logs, no cleanup. This violates the project's own safety rule: all throwing ObjC code must live inside .m files with @try/@catch/@autoreleasepool. The Tokio-level timeout cancellation escaped that boundary.

Why it didn't reproduce everywhere: OCR completes under 500ms on most screen content; only specific content types (complex fonts, image-heavy pages) push Vision past 500ms and trigger the race.

The fix

Three changes:

  • Dropped the Tokio-level OCR timeout. The blocking Vision call now runs to completion inside spawn_blocking. Vision returns in tens of milliseconds in the overwhelming majority of cases; outlier calls taking a second or two are vastly preferable to crashing the daemon. This is the actual fix.
  • Installed std::panic::set_hook at daemon startup that logs panics via tracing::error! with location + payload before process exit. Rust's default panic printer writes to stderr, which was being redirected to /dev/null — so any Tokio task panic vanished without a trace.
  • Redirected daemon stderr to daemon.stderr.log in both the Swift menu bar app's Process() spawn and the Rust CLI's Command::spawn path (previously /dev/null). Future ObjC NSException messages and panic backtraces will leave a diagnostic trail for the next mystery exit.

Verification

  • Daemon starts cleanly at v0.2.8
  • daemon.stderr.log file created in ~/Library/Application Support/agenthandover/logs/ (empty — waiting for any future abnormal exit)
  • 3000/3000 Python tests pass
  • Signed + notarized + stapled

Install

:::bash
brew upgrade --cask agenthandover

# Or direct download
curl -LO https://github.com/sandroandric/AgentHandover/releases/download/v0.2.8/AgentHandover-0.2.8.pkg
sudo installer -pkg AgentHandover-0.2.8.pkg -target /

Relates to issue [#1] (NOT closing — waiting for reporter's v0.2.8 retest).

Source: README.md, updated 2026-04-14