Repository navigation
Description
Steps to reproduce
AnalysisOptionsMigration (added in #187940, shipped in 3.47.0) writes a fixed seven-entry exclusion list. It is a const with no relationship to what the project actually contains (analysis_options_migration.dart#L39-L47), so web/** is written into every project — including ones where web/ is a source directory rather than a platform scaffold.
web/ is where the official dart create -t web template puts main.dart. One flutter pub get takes it out of analysis:
$ dart create -t web webrepro && cd webrepro
$ ls -1
analysis_options.yaml
CHANGELOG.md
pubspec.lock
pubspec.yaml
README.md
web
$ printf 'int probe() => "nope";\n' >> web/main.dart
$ dart analyze
error - web/main.dart:10:16 - A value of type 'String' can't be returned from the function 'probe' because it has a return type of 'int'. - return_of_invalid_type
1 issue found.
$ flutter pub get
Resolving dependencies...
Downloading packages...
Got dependencies!
Upgrading analysis_options.yaml to exclude build and platform directories.
$ dart analyze
Analyzing webrepro...
No issues found!The package has no android/, ios/, macos/, linux/ or windows/ directory at all. The only one of the seven names it does have is web/, which holds its code — and that is the one the migration takes out of analysis. The output names no paths, so the loss of coverage is silent, and the error stays hidden until someone reads the rewritten analysis_options.yaml.
flutter pub get in a Dart-only package is not contrived: it is what happens in mixed Flutter/Dart monorepos and Dart workspaces, where the Flutter app and the pure-Dart packages sit side by side. The same layout is described in this comment on #191056.
The same unconditional list in a Flutter app
The mechanism is visible without any web package — a project created for two platforms gets exclusions for the four it does not have, and cannot keep them out:
$ flutter create --platforms=android,ios repro_ao
$ ls -1 repro_ao
analysis_options.yaml
android
ios
lib
pubspec.lock
pubspec.yaml
README.md
repro_ao.iml
test
$ sed -n '/^analyzer:/,/^$/p' repro_ao/analysis_options.yaml
analyzer:
exclude:
- build/**
- android/**
- ios/**
- web/**
- windows/**
- macos/**
- linux/**
$ # reduce the exclude list to `- build/**`, then:
$ flutter analyze
Upgrading analysis_options.yaml to exclude build and platform directories.
Analyzing repro_ao...
No issues found! (ran in 3.4s)
$ sed -n '/^analyzer:/,/^$/p' repro_ao/analysis_options.yaml # all seven are backProjectMigrator has no opt-out, and the migration runs from ensureReadyForPlatformSpecificTooling, so flutter pub get, flutter analyze, flutter run and flutter build all restore the entries. The flutter create templates emit the same fixed list (templates/app/analysis_options.yaml.tmpl).
This is adjacent to #191056 (fixed by #191082), which stopped the list from being re-applied when it is already inherited through include:. That fix does not change any of the above — excludesToExclude on master is still the same unconditional list.
Expected results
Exclusions are written only for directories the project actually uses as platform scaffolds. A Dart web package keeps web/ analyzed; a project created with --platforms=android,ios gets build/**, android/** and ios/**.
Actual results
All seven entries are written unconditionally, by both the flutter create templates and the migration, and the migration restores them on every command that reaches ensureReadyForPlatformSpecificTooling.
Suggested fix
For the platform scaffolds, FlutterProject already exposes per-platform existence through FlutterProjectPlatform.existsSync() ("Whether the platform exists in the project"), used elsewhere as project.android.existsSync() / project.linux.existsSync() (project_validator.dart:63-67, flutter_plugins.dart:1199):
final excludesToExclude = <String>[
'build/**',
if (project.android.existsSync()) 'android/**',
if (project.ios.existsSync()) 'ios/**',
if (project.windows.existsSync()) 'windows/**',
if (project.macos.existsSync()) 'macos/**',
if (project.linux.existsSync()) 'linux/**',
];That alone does not cover the web/ case: WebProject.existsSync() is web/ plus web/index.html (project.dart:1186-1188), and a dart create -t web package satisfies both. The discriminator there is that it is not a Flutter project at all — its pubspec.yaml has no flutter key, so project.manifest.dependencies does not contain flutter. Skipping the migration entirely in that case seems the cleaner guard, and it would also stop the tool from editing a checked-in config file in packages that never asked for Flutter tooling.
If neither gating is wanted, an opt-out would at least make the current behaviour livable — today the only way to keep the entries out of a checked-in analysis_options.yaml is to not run any Flutter command.
Flutter Doctor output
Doctor output
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.47.0, on macOS 26.5.2 25F84 darwin-arm64, locale en-KR)
[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0)
[✓] Xcode - develop for iOS and macOS (Xcode 26.6)
[✓] Chrome - develop for the web
[✓] Connected device (5 available)
[✓] Network resources
• No issues found!
Activity
mbcorona commentedon Aug 15, 2026
Thanks for the report. Confirmed from the source, routing to the tool team for further investigation.
Only exclude platform directories that actually exist in analysis_opt…
Only exclude platform directories that actually exist in analysis_opt…
[stable] Only exclude platform directories that actually exist in ana…
[beta] Only exclude platform directories that actually exist in analy…
github-actions commentedon Sep 2, 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.