Repository navigation
Description
Steps to reproduce
The whole reproduction is five CLI commands from a fresh flutter create — no project download needed:
flutter create --platforms web repro && cd repro
# 1. Start with any web-enabled plugin and build once.
flutter pub add url_launcher
flutter build web --release
# 2. Add a second plugin, exactly like a project evolving over time.
flutter pub add package_info_plus
# 3. Build again — same command, same machine, no clean in between.
flutter build web --release
# 4. Inspect the registrant the build just compiled:
grep registerWith .dart_tool/flutter_build/*/web_plugin_registrant.dartActual result (after step 3 — the second build "succeeds"):
UrlLauncherPlugin.registerWith(registrar);PackageInfoPlusWebPlugin.registerWith(...) is missing. The generated web_plugin_registrant.dart was not regenerated when the plugin set changed; the build directory (same hash, e.g. .dart_tool/flutter_build/522e0d9d…) was reused with the stale file, and the stale registrant is what gets compiled into main.dart.js. The newly added plugin's web implementation silently never registers in the shipped app.
Expected result:
PackageInfoPlusWebPlugin.registerWith(registrar);
UrlLauncherPlugin.registerWith(registrar);which is exactly what you get after flutter clean && flutter build web --release.
The loud inverse variant (proves the staleness directly)
Going the other direction fails loudly instead of silently. In a project that has built web release with N plugins, remove a plugin from pubspec.yaml, flutter pub get, and rebuild:
Target dart2js failed: …
.dart_tool/flutter_build/<hash>/web_plugin_registrant.dart:15:8:
Error: Not found: 'package:package_info_plus/src/package_info_plus_web.dart'
The build tries to compile the stale registrant, which still imports the removed package. Same root cause, opposite symptom: the registrant target is not invalidated when the plugin set changes (as long as both states have ≥ 1 plugin, so the build-directory hash stays the same — going from zero plugins to some plugins changes the hash and regenerates correctly, which is why single-shot fresh repros never catch this).
How this bites in production (real-world impact)
We hit this in a real app and it was brutal to diagnose:
-
The app used Firebase (web) and was developed for weeks with
flutter run -d chrome— debug is unaffected (different build pipeline/artifacts), so everything worked daily. -
The first-ever
flutter build web --release+ deploy produced a blank white screen. The project's long-lived.dart_toolstill contained registrants generated before the Firebase plugins were added (we later found multiple staleweb_plugin_registrant.dartfiles from different eras of the project sitting in differentflutter_build/<hash>/dirs). -
With no
FirebaseCoreWebregistered,FirebasePlatform.instancesilently fell back to the method-channel default andFirebase.initializeApp()threw:PlatformException(channel-error, Unable to establish connection on channel: "dev.flutter.pigeon.firebase_core_platform_interface.FirebaseCoreHostApi.initializeCore")which killed
main()beforerunApp→ pure white page. In minified release the console shows only a bare"Uncaught ", so we had to attach CDP to extract the Dart exception. -
The misdirection potential is extreme:
flutter build web --profileof the same tree worked (different build args → differentflutter_build/<hash>dir → freshly generated registrant), which made it look like a dart2js/minification bug. Checking out an older commit into a fresh worktree also "worked" (clean dir), which made it look like a recently-added package was the culprit. Both were red herrings — it was the stale registrant all along. -
Existing reports of
channel-error …initializeCoreare all mobile-native registration problems; we found no issue describing this web build-cache mechanism, and searching for the actual cause is nearly impossible from the symptom.
Anyone with a long-lived checkout who added a web-relevant plugin after their first web build, and whose CI/dev flow doesn't happen to run flutter clean, can ship an app with silently unregistered plugins (MissingPluginException / channel-error / features no-oping at runtime).
Workarounds
flutter cleanbefore release web builds (or delete.dart_tool/flutter_build), or- register web plugin implementations explicitly in
main()via a conditional-import shim that mirrors the generated registrant (registering twice is harmless).
Expected behaviour
flutter build web should regenerate (or invalidate) web_plugin_registrant.dart whenever the resolved plugin set changes — plugin additions/removals in pubspec.yaml must be part of the fingerprint that decides whether the registrant (and the kernel/dart2js outputs that depend on it) are up to date.
Flutter Doctor output
flutter doctor -v
[✓] Flutter (Channel stable, 3.41.9, on macOS 26.5.1 25F80 darwin-arm64, locale en-CA)
• Flutter version 3.41.9 on channel stable at /Users/…/flutter
• Framework revision 00b0c91f06 (2026-04-29)
• Engine revision 42d3d75a56
• Dart version 3.11.5
• DevTools version 2.54.2
Dart SDK version: 3.11.5 (stable)
Reproduced with flutter build web --release (also applies with default args; debug flutter run is unaffected; --profile appeared unaffected only because the different args select a different cached build directory).
Transparency note: this bug was diagnosed and this report was written by Claude (Anthropic's Fable 5 model) working in Claude Code, at the request and with the permission of this account's owner, who asked that the investigation be credited accurately. Happy to provide any additional detail from the diagnostic session (CDP traces, the stale registrant files from the affected project, etc.) via the account owner.
Activity
forTheSakeOfLogic commentedon Jul 8, 2026
Prior-art survey (searched GitHub, StackOverflow, and the wider web for earlier reports of this mechanism):
pubspec.yamlplugin change — was reported and fixed for the hot-restart path back then. This issue is the same failure class surviving in theflutter build webpath: the registrant target is not invalidated when the plugin set changes between two non-empty states.package_info_pluspast the version where the separatepackage_info_plus_webpackage was discontinued, the user'sflutter build web --releasefailed compiling a.dart_tool/flutter_build/<hash>/web_plugin_registrant.dartthat still imported the discontinuedpackage_info_plus_web— i.e., a stale registrant from before the upgrade. It was closed as a user import error, but the offending import is in the generated file, andflutter cleanwould have "fixed" it — consistent with the mechanism described here.web_plugin_registrantat all; the symptoms appear to get absorbed by generic "runflutter clean" folklore without the tool bug being identified, which is likely why the silent add-a-plugin variant hasn't been reported until now.suryaKommana2662 commentedon Jul 9, 2026
@forTheSakeOfLogic Thank you for this exceptionally high-quality and thorough bug report. Providing a 5-command reproduction case makes it very easy to verify.
I have reproduced the issue and confirmed that the build-cache fingerprinting logic in
flutter_toolsis failing to invalidate the stale registrant file. I am labeling and routing this to theteam-toolto investigate.[flutter_tools] Invalidate WebEntrypointTarget when plugin set changes
[flutter_tools] Invalidate WebEntrypointTarget when plugin set changes (
github-actions commentedon Aug 6, 2026
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.