Repository navigation
Description
Filing per the stale-bot's closing instruction on #183900 ("If you find this problem please file a new issue with the same description, what happens, logs and the output of flutter doctor -v").
The reason this is worth a fresh issue: #183900 was closed after the discussion converged on "debug cold start, which is not currently supported" (comment). This report is a --release build downloaded from TestFlight/App Store, on GA iOS. JIT/debug is not involved, so that explanation does not cover it. There is also a 2026-07-13 comment on the closed issue reproducing it on GA iOS 26.5.2 with Flutter 3.44.6, i.e. after the close.
Steps to Reproduce
I do not have a minimal reproduction — this is a production app, and I develop on Windows with iOS builds on CI, so I cannot bisect on-device. What I can state precisely is the configuration and the fault, below. The crash is 100% deterministic on the affected device: iOS relaunched the app automatically and it died identically 4 seconds later.
- Flutter 3.41.4 app, UIScene lifecycle, implicit engine (stock
Main.storyboardwithcustomClass="FlutterViewController"+FlutterImplicitEngineDelegate). flutter build ipa --release, upload to App Store Connect, install via TestFlight/App Store.- Cold launch from the home screen, untethered, on a ProMotion (120 Hz) device.
Expected results
App launches.
Actual results
EXC_BAD_ACCESS (SIGSEGV) at 0x0 roughly 0.15 s after process launch, inside -[VSyncClient initWithTaskRunner:callback:].
Attaching the debugger masks it — it slows startup enough that the engine finishes attaching before viewDidLoad runs. This matches what others reported on #183900.
Analysis
I disassembled the instructionByteStream embedded in the .ips. At PC:
LDR X8, [X20] <-- faults; X20 == 0 (threadState x[20] == 0x0)
LDR X8, [X8] <-- first vtable slot
ADD X1, SP, #0x18 <-- arg1 = the std::function closure built immediately above
MOV X0, X20 <-- arg0 = this
BLR X8 <-- virtual call
and immediately before PC:
LDR X21, [X19, #0x28] ; MOV X0, X21 ; BL <objc_retain>
LDR X20, [X20] <-- deref the RefPtr, yields 0
ADRP/ADD X8, <vtable for std::__fl::__function::__func<-[VSyncClient initWithTaskRunner:callback:]::$_1, ...>>
STP X8, X21, [SP, #0x18] <-- build the closure capturing the retained CADisplayLink
So this is task_runner->PostTask(closure) — vsync_waiter_ios.mm:86 — a virtual call through a null fml::TaskRunner. The register dump corroborates it independently: x8 is symbolized by the crash reporter as exactly that lambda's std::function vtable, far = 0x0, esr = 0x92000006 (data abort, byte read, translation fault).
The chain:
-[FlutterViewController createTouchRateCorrectionVSyncClientIfNeeded](FlutterViewController.mm:1391-1392) passesself.engine.platformTaskRunnerstraight into-[VSyncClient initWithTaskRunner:callback:].-[FlutterEngine platformTaskRunner]returns an emptyfml::RefPtrwhen_shellis null (FlutterEngine.mm:440-445).-[VSyncClient initWithTaskRunner:callback:]guards it only withFML_DCHECK(task_runner)(vsync_waiter_ios.mm:73), which is compiled out in release, then dereferences it at:86.
So in release there is no guard at all between "engine has no shell" and a null-pointer virtual call.
Why ProMotion only: FlutterViewController.mm:1380 early-returns when DisplayLinkManager.displayRefreshRate < 60.1. On a 60 Hz device or the Simulator this line is never reached, so the same underlying state stays invisible. Testing on non-ProMotion hardware proves nothing here.
What I could not determine is why the engine has no shell at viewDidLoad — and I want to be upfront that a strict reading of the 3.41.4 source seems to exclude every candidate state I could enumerate:
_enginebeing nil:awakeFromNibalways callssharedSetupWithProject:when_engineis nil, and that method's only early return needs[[FlutterEngine alloc] initWithName:...]to return nil, whose only nil path is theEnableTracingIfNecessaryguard — andptrace_check.hmakes that a literalreturn trueoutsideFLUTTER_RUNTIME_MODE_DEBUG.- A non-nil engine with a null shell:
sharedSetupWithProject:sets_engineNeedsLaunch = YESat:331, soviewDidLoadwould take thelaunchEnginebranch first, and a null shell there faults at a non-zero offset insideShell::RunEngine(which readstask_runners_.GetPlatformTaskRunner()), not at0x0insideVSyncClient.
One of those premises must be wrong on iOS 26.x. Two things I noticed that might be worth a look from someone who can run this on-device:
-[FlutterEngine createShell:libraryURI:initialRoute:]is called atFlutterViewController.mm:302with itsBOOLreturn discarded, and_engineNeedsLaunch = YESis set unconditionally at:331regardless.-[FlutterEngine run]by contrast gates the launch on shell-creation success. A silentcreateShellfailure on the implicit path would therefore be invisible until something dereferences the shell.FlutterViewController'sengineAllowHeadlessExecutionhas no implementation in the framework (declaredFlutterViewController.h:255, used:290), so it auto-synthesizes toNO— meaning the implicit engine callsdestroyContext/_shell.reset()when its controller deallocates (FlutterEngine.mm:553-563). That looks like a plausible mechanism for the scene-reconnect variant reported on [iOS 26 Beta] Crash (SIGSEGV) in -[VSyncClient initWithTaskRunner:callback:] on ProMotion devices using implicit engine #183900 (background 20+ min, return, iOS having discarded and recreated the scene): a live engine with a reset shell.
Configuration is stock
Worth stating explicitly, because the obvious first guess is a botched UIScene migration. I diffed our iOS config against packages/flutter_tools/templates/app/ios.tmpl/ in the 3.41.4 SDK and it matches:
Info.plist— the template itself ships bothUISceneStoryboardFile=Maininside the scene config (Info.plist.tmpl:44-45) and top-levelUIMainStoryboardFile=Main(:54-55), plusCADisableMinimumFrameDurationOnPhone(:5-6). Ours had all three.AppDelegate.swift— identical to the template's (FlutterAppDelegate+FlutterImplicitEngineDelegate, registering indidInitializeImplicitFlutterEngine) apart from two push-notification lines.Main.storyboard— unmodified template storyboard, singleFlutterViewControllerinitial VC.- Only deviation: we named
FlutterSceneDelegatedirectly inInfo.plistwhere the template uses an emptyclass SceneDelegate: FlutterSceneDelegate {}behind$(PRODUCT_MODULE_NAME).SceneDelegate. Behaviourally equivalent.
CADisableMinimumFrameDurationOnPhone is not the trigger, in case that comes up: it is read only by +[DisplayLinkManager maxRefreshRateEnabledOnIPhone] and consumed by -[VSyncClient setMaxRefreshRate:]. The branch deciding whether to build the VSyncClient at all reads DisplayLinkManager.displayRefreshRate, which falls through to UIScreen.mainScreen.maximumFramesPerSecond regardless of the plist. Removing it loses 120 Hz and still crashes.
Workaround
Abandoning the implicit engine fixes it — consistent with what others found on #183900. Create and run() an explicit FlutterEngine in application(_:didFinishLaunchingWithOptions:), register plugins against it there, and use a SceneDelegate: FlutterSceneDelegate that builds the window with FlutterViewController(engine:). -initWithEngine: sets _engineNeedsLaunch = NO and calls setViewController:, so viewDidLoad takes the engine.viewController == self branch against an already-shelled engine and the null runner becomes unreachable.
That is a bypass, not a fix. Regardless of the underlying cause, I'd argue vsync_waiter_ios.mm:86 should not be an unguarded deref in release — #184639 proposed exactly that and was closed unmerged. The counter-argument there was that silencing it would mask the real problem; the error screen suggested in that thread instead of a SIGSEGV would satisfy both, and would have made this far cheaper to diagnose than disassembling .ips byte streams.
Crash log
Thread 0 (crashed), com.apple.main-thread. Device identifiers redacted; otherwise verbatim.
Incident Identifier: <redacted>
Hardware Model: iPhone18,2
Process: Runner [13586]
Version: 2.10.0 (51)
Code Type: ARM-64 (Native)
Role: Foreground
OS Version: iPhone OS 26.5.2 (23F84)
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
Exception Codes: 0x0000000000000001, 0x0000000000000000
VM Region Info: 0 is not in any region. Bytes before following region: 4339662848
Termination Reason: SIGNAL 11 Segmentation fault: 11
Triggered by Thread: 0
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed: (symbol + symbolLocation, verbatim from the .ips `frames` array;
load addresses omitted rather than reconstructed)
0 Flutter -[VSyncClient initWithTaskRunner:callback:] + 300
1 Flutter -[FlutterViewController createTouchRateCorrectionVSyncClientIfNeeded] + 216
2 Flutter -[FlutterViewController viewDidLoad] + 396
3 UIKitCore -[UIViewController _sendViewDidLoadWithAppearanceProxyObjectTaggingEnabled] + 84
4 UIKitCore -[UIViewController loadViewIfRequired] + 968
5 UIKitCore -[UIViewController view] + 24
6 UIKitCore -[UIWindow addRootViewControllerViewIfPossible] + 144
7 UIKitCore -[UIWindow _updateLayerOrderingAndSetLayerHidden:actionBlock:] + 180
8 UIKitCore -[UIWindow _setHidden:forced:] + 260
9 UIKitCore -[UIWindow _mainQueue_makeKeyAndVisible] + 40
10 UIKitCore -[UIWindowScene _performDeferredInitialWindowUpdateForConnection] + 216
11 UIKitCore +[UIScene _sceneForFBSScene:create:withSession:connectionOptions:] + 1196
12 UIKitCore -[UIApplication _connectUISceneFromFBSScene:transitionContext:] + 1200
13 UIKitCore -[UIApplication workspace:didCreateScene:withTransitionContext:completion:] + 328
14 UIKitCore -[UIApplicationSceneClientAgent scene:didInitializeWithEvent:completion:] + 304
15 FrontBoardServices __95-[FBSScene _callOutQueue_didCreateWithTransitionContext:alternativeCreationCallout:completion:]_block_invoke + 364
16 FrontBoardServices -[FBSScene _callOutQueue_maybeCoalesceClientSettingsUpdates:] + 132
17 FrontBoardServices -[FBSScene _callOutQueue_didCreateWithTransitionContext:alternativeCreationCallout:completion:] + 432
18 FrontBoardServices __93-[FBSWorkspaceScenesClient _callOutQueue_sendDidCreateForScene:transitionContext:completion:]_block_invoke.349 + 252
19 FrontBoardServices -[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 176
20 FrontBoardServices -[FBSWorkspaceScenesClient _callOutQueue_sendDidCreateForScene:transitionContext:completion:] + 472
21 FrontBoardServices __92-[FBSWorkspaceScenesClient createSceneWithIdentity:parameters:transitionContext:completion:]_block_invoke_2 + 256
22 FrontBoardServices -[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 176
23 libdispatch.dylib _dispatch_client_callout + 16
24 libdispatch.dylib _dispatch_block_invoke_direct + 284
25 BoardServices __BSSERVICEMAINRUNLOOPQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 52
26 BoardServices BSServiceMainRunLoopSourceHandler + 224
27 CoreFoundation __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
28 CoreFoundation __CFRunLoopDoSource0 + 172
29 CoreFoundation __CFRunLoopDoSources0 + 332
30 CoreFoundation __CFRunLoopRun + 820
31 CoreFoundation _CFRunLoopRunSpecificWithOptions + 532
32 GraphicsServices GSEventRunModal + 120
33 UIKitCore -[UIApplication _run] + 796
34 UIKitCore UIApplicationMain + 332
35 UIKitCore <imageOffset 2721304>
36 Runner static UIApplicationDelegate.main() + 128
37 Runner static AppDelegate.$main() + 44
38 Runner main + 28
39 dyld start + 6928
Thread 0 crashed with ARM Thread State (64-bit):
x20: 0x0000000000000000 <-- the null fml::TaskRunner
x8: symbolized by the crash reporter as
"vtable for std::__fl::__function::__func<-[VSyncClient initWithTaskRunner:callback:]::$_1,
std::__fl::allocator<-[VSyncClient initWithTaskRunner:callback:]::$_1>, void ()>"
(symbolLocation 16)
esr: 0x92000006 (Data Abort) byte read Translation fault
far: 0x0000000000000000
pc: matchesCrashFrame = 1
instructionByteStream.atPC (base64, as emitted by the crash reporter — this is what I disassembled):
iAJA+QgBQPnhYwCR4AMUqgABP9bgG0D5HwAW64AAAFTgAAC0CAWAUg==
instructionByteStream.beforePC:
dRZA+eADFapjECWUlAJA+WEQJZQI9wDQCMEikejXAan2YwCR9hsA+Q==
Binary Images (uuid + path; the two that matter):
<e99862a0-6d8c-38ef-b0ff-083f80599e32> arm64 Runner
.../Runner.app/Runner
<4c4c441b-5555-3144-a1fa-318e70efe420> arm64 Flutter
.../Runner.app/Frameworks/Flutter.framework/Flutter
bundleInfo: {"CFBundleShortVersionString":"2.10.0","CFBundleVersion":"51","DTAppStoreToolsBuild":"17F106"}
storeInfo: {"thirdParty":true, ...} — i.e. installed from the store, not a debug install.
flutter doctor -v
[√] Flutter (Channel stable, 3.41.4, on Microsoft Windows [Version 10.0.26200.8875], locale en-US) [579ms]
• Flutter version 3.41.4 on channel stable at C:\...\flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision ff37bef603 (5 months ago), 2026-03-03 16:03:22 -0800
• Engine revision e4b8dca3f1
• Dart version 3.11.1
• DevTools version 2.54.1
• Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop,
enable-android, enable-ios, cli-animations, enable-native-assets, omit-legacy-version-file,
enable-lldb-debugging, enable-uiscene-migration
[√] Windows Version (11 Home 64-bit, 25H2, 2009) [1,923ms]
[√] Android toolchain - develop for Android devices (Android SDK version 36.1.0) [4.6s]
• Android SDK at C:\Users\...\Android\sdk
• Platform android-36.1, build-tools 36.1.0
• Java version OpenJDK Runtime Environment (build 21.0.9+-14649483-b1163.86)
• All Android licenses accepted.
[√] Chrome - develop for the web [273ms]
[√] Visual Studio - develop Windows apps (Visual Studio Build Tools 2022 17.14.35 (June 2026)) [271ms]
[√] Connected device (3 available) [2.5s]
[√] Network resources [669ms]
• No issues found!
Note: I develop on Windows; the iOS builds are produced on macOS CI (Codemagic, Xcode 26, flutter build ipa --release), so flutter doctor shows no Xcode section. Deployment target iOS 15, use_frameworks! :linkage => :static, use_modular_headers!.
Related: #183900 (closed), #184639 (closed unmerged), #182108.
Activity
gaaclarke commentedon Jul 27, 2026
We've had problems with promotion in the past since it got added after-the-fact for some of this logic. Let us know if you need someone to bounce ideas off of. I don't have anything to add, someone will need to investigate it.
iOS: Add null-check for touch rate correction task runner
iOS: Add null check for touch rate correction task runner (flutter#19…