Found while moving Android's report-handler dispatch off the main thread (bugsee-android#146). Latent today only because that dispatch ran inline on the main thread — once handlers run on a background thread, this becomes a live JNI misuse.
The defect
CallbackProxies.cs:54-117 enqueues the JNI references for report and completion onto a queue drained later in Unity's Update(). Those are local references: the JNI spec only guarantees them for the duration of the native call that produced them. Dereferencing them from another thread, after the proxy method has returned, is undefined behaviour — in practice a crash or a silently wrong object once the local frame is popped.
EventFilterProxy.filter has the same shape.
Why it has not bitten yet
Android dispatched report handlers on the main thread and, on the dialog-submit path, inline in the caller's frame. Unity's Update() runs on that same thread, so the drain happened to occur while the frame was still live. That is luck, not correctness — and bugsee-android#146 removes it by dispatching handlers on a dedicated background thread.
Fix
Either promote to global references (NewGlobalRef on enqueue, DeleteGlobalRef after the drain — with care not to leak if the queue is discarded), or use MainThreadDispatcher.RunSync so the dereference happens before the proxy call returns and the local frame is still valid.
The second is simpler but reintroduces a main-thread hop, which is the thing Android just removed — so on a wedged main thread the handler would not run. Global refs keep the benefit.
Scope
cross/bugsee-unity (UPM package, branch cursor/upm-package-scaffold) — not shipped, so there is no released version affected. The legacy cross/unity wrapper registers only metadata and does not implement ReportHandler, so it is unaffected.
Verified unaffected for the same change: Flutter (hops to main itself), .NET and KMP (run the host callback inline, no Handler/UI calls), React Native / Capacitor / Cordova (do not implement ReportHandler).
🤖 Generated with Claude Code
Found while moving Android's report-handler dispatch off the main thread (bugsee-android#146). Latent today only because that dispatch ran inline on the main thread — once handlers run on a background thread, this becomes a live JNI misuse.
The defect
CallbackProxies.cs:54-117enqueues the JNI references forreportandcompletiononto a queue drained later in Unity'sUpdate(). Those are local references: the JNI spec only guarantees them for the duration of the native call that produced them. Dereferencing them from another thread, after the proxy method has returned, is undefined behaviour — in practice a crash or a silently wrong object once the local frame is popped.EventFilterProxy.filterhas the same shape.Why it has not bitten yet
Android dispatched report handlers on the main thread and, on the dialog-submit path, inline in the caller's frame. Unity's
Update()runs on that same thread, so the drain happened to occur while the frame was still live. That is luck, not correctness — and bugsee-android#146 removes it by dispatching handlers on a dedicated background thread.Fix
Either promote to global references (
NewGlobalRefon enqueue,DeleteGlobalRefafter the drain — with care not to leak if the queue is discarded), or useMainThreadDispatcher.RunSyncso the dereference happens before the proxy call returns and the local frame is still valid.The second is simpler but reintroduces a main-thread hop, which is the thing Android just removed — so on a wedged main thread the handler would not run. Global refs keep the benefit.
Scope
cross/bugsee-unity(UPM package, branchcursor/upm-package-scaffold) — not shipped, so there is no released version affected. The legacycross/unitywrapper registers only metadata and does not implementReportHandler, so it is unaffected.Verified unaffected for the same change: Flutter (hops to main itself), .NET and KMP (run the host callback inline, no Handler/UI calls), React Native / Capacitor / Cordova (do not implement
ReportHandler).🤖 Generated with Claude Code