Skip to content

[flutter_tools] Safely handle broken symlinks and existing files during plugin symlink creation - #191496

Merged
auto-submit[bot] merged 8 commits into
flutter:masterfrom
bkonyi:investigation/issue-90028
Sep 1, 2026
Merged

auto-submit[bot] merged 8 commits into
flutter:masterfrom
bkonyi:investigation/issue-90028

Conversation

@bkonyi

@bkonyi bkonyi commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

Description

Fixes a crash during plugin symlink creation on Windows/Linux desktop builds (FileSystemException: Creation failed, OS Error: A file cannot be created if it already exists., errno = 183 / errno = 17).

Cause

Previously, _createPlatformPluginSymlinks checked if (link.existsSync()) continue; before calling link.createSync(path).

  1. If a non-link entity (such as a regular directory or file from manual extraction, git checkout, or previous builds) occupied the symlink path, Link.existsSync() returned false. The tool then called link.createSync(path), crashing with ERROR_ALREADY_EXISTS (errno = 183) on Windows or EEXIST (errno = 17) on POSIX.
  2. Link.existsSync() did not check if an existing symlink pointed to the expected plugin target path. If a plugin's path changed (e.g. a version upgrade in pub cache), existing symlinks pointing to outdated paths were skipped.

Fix

  • Uses typeSync(link.path, followLinks: false) to inspect disk state type-agnostically before symlink creation.
  • Canonicalizes and compares link.targetSync() with path. If matching, creation is skipped.
  • Deletes stale or mismatched links via ErrorHandlingFileSystem.deleteIfExists(link), and removes conflicting non-link entries before calling link.createSync(path).

Related Issues

Tests

  • packages/flutter_tools/test/general.shard/plugins_test.dart:
    • createPluginSymlinks repairs broken symlinks without failing
    • createPluginSymlinks replaces existing files with symlinks without failing

bkonyi added 3 commits August 20, 2026 23:20
…ng plugin symlink creation

In Dart, Link.existsSync() evaluates whether the link's destination target exists rather than checking if a symlink file entry exists on disk. If a plugin was removed, moved, or cleaned, Link.existsSync() returned false for broken symlinks. The tool then called Link.createSync(path), which crashed with ERROR_ALREADY_EXISTS (errno = 183) on Windows or EEXIST (errno = 17) on POSIX.

This fix:
1. Uses typeSync(followLinks: false) to detect existing links and files type-agnostically in _createPlatformPluginSymlinks.
2. Removes stale, broken, or mismatched links and files prior to creating the new symlink.
3. Updates copyDirectory in file_system.dart to safely clean up conflicting destination entities before copying links or files.
4. Adds automated unit tests covering broken symlink recovery and existing file replacement.

Fixes flutter#90028
Related to flutter#131978
Related to flutter#74227
…h pattern matching

Uses Dart 3 pattern matching and switch statements on entity types to simplify conflict detection and file system traversal.
@bkonyi bkonyi added the CICD Run CI/CD label Aug 21, 2026
@github-actions github-actions Bot added the tool Affects the "flutter" command-line tool. See also t: labels. label Aug 21, 2026
@bkonyi
bkonyi marked this pull request as ready for review August 21, 2026 19:28

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates directory copying and platform plugin symlink creation to clean up conflicting destination entities and broken symlinks before writing new files or links. It also adds corresponding unit tests. The review feedback suggests performing conflict checks and deletions lazily in copyDirectory to prevent deleting existing destination files when copy filters return false, and using path canonicalization when comparing symlink targets to avoid platform-specific path mismatches.

Comment thread packages/flutter_tools/lib/src/base/file_system.dart Outdated
Comment thread packages/flutter_tools/lib/src/flutter_plugins.dart Outdated
bkonyi added 2 commits August 21, 2026 19:39
…paths and scope changes to flutter_plugins

- Use canonicalize on symlink target and path before comparison.
- Revert copyDirectory modifications to keep the fix minimal and focused strictly on plugin symlink creation.
@bkonyi
bkonyi requested a review from stuartmorgan-g August 21, 2026 20:05
@stuartmorgan-g

stuartmorgan-g commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

In Dart, Link.existsSync() follows symlinks to evaluate whether the destination target exists rather than checking if a symlink file entry exists on disk.

Is there a source for this, or an issue filed? This seems contrary to the documented behavior:

Calling existsSync on an instance of one of these subclasses checks whether the object exists in the file system object exists and is of the correct type (file, directory, or link).

It can't possibly be following links before determining whether the link is the correct type since it would then never be true for links, but evaluating different files for the two steps seems extremely counterintuitive.

I also wasn't able to repro that behavior in a trivial Dart script on macOS.

@bkonyi

bkonyi commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

In Dart, Link.existsSync() follows symlinks to evaluate whether the destination target exists rather than checking if a symlink file entry exists on disk.

Is there a source for this, or an issue filed? This seems contrary to the documented behavior:

Calling existsSync on an instance of one of these subclasses checks whether the object exists in the file system object exists and is of the correct type (file, directory, or link).

It can't possibly be following links before determining whether the link is the correct type since it would then never be true for links, but evaluating different files for the two steps seems extremely counterintuitive.

I also wasn't able to repro that behavior in a trivial Dart script on macOS.

Yeah, this isn't the right explanation. The real issue is when there's already existing file system entities that aren't symlinks, which causes link.existsSync() to return false. This results in the tool trying to create a new link, which fails since there's already a file system entity with that name.

This is the original exception and stack trace: #90028 (comment)

Here's a reproduction script:

import 'dart:io';

void main() {
  final tempDir = Directory.systemTemp.createTempSync('flutter_issue_90028_repro_');
  try {
    final symlinkDir = Directory('${tempDir.path}/.plugin_symlinks')..createSync();
    final pluginDir = Directory('${tempDir.path}/cache/some_plugin')..createSync(recursive: true);

    // Scenario: A non-link file or directory already occupies the plugin path
    // (e.g. from archive extraction, manual copy, git checkout, or previous builds).
    final conflictingEntity = File('${symlinkDir.path}/some_plugin')
      ..writeAsStringSync('stale non-link file');

    final link = Link('${symlinkDir.path}/some_plugin');

    print('Conflicting file exists: ${conflictingEntity.existsSync()}');
    print('link.existsSync() = ${link.existsSync()}');

    // Original unfixed Flutter implementation from _createPlatformPluginSymlinks:
    if (!link.existsSync()) {
      print('link.existsSync() returned false. Calling link.createSync()...');
      link.createSync(pluginDir.path);
    }
  } on FileSystemException catch (e) {
    print('\n=== CRASH REPRODUCED ===');
    print('Caught FileSystemException:');
    print('  OS Error: ${e.osError}');
    print('  Path: ${e.path}');
  } finally {
    tempDir.deleteSync(recursive: true);
  }
}

Output on Linux:

Temporary test directory: /tmp/flutter_issue_90028_repro_CUSEGP

1. Conflicting entity exists at: /tmp/flutter_issue_90028_repro_CUSEGP/.plugin_symlinks/some_plugin
   Is regular file: true
   link.existsSync() = false

2. Attempting original Flutter logic:
   if (link.existsSync()) continue;
   link.createSync(path);
   -> link.existsSync() returned false, calling link.createSync()...

=== CRASH REPRODUCED ===
Caught FileSystemException:
  Message: Cannot create link
  OS Error: OS Error: File exists, errno = 17
  Path: /tmp/flutter_issue_90028_repro_CUSEGP/.plugin_symlinks/some_plugin
========================

I've updated the description and documentation to reflect the actual cause of the issue.

@stuartmorgan-g stuartmorgan-g left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@bkonyi bkonyi added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 31, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Aug 31, 2026
@auto-submit

auto-submit Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/191496, because - The status or check suite Dashboard Checks has failed. Please fix the issues identified (or deflake) before re-applying this label.

@bkonyi bkonyi added the autosubmit Merge PR when tree becomes green via auto submit App label Sep 1, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Sep 1, 2026
Merged via the queue into flutter:master with commit 00bd762 Sep 1, 2026
23 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Sep 1, 2026
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Sep 1, 2026
flutter/flutter@c8c5e3b...0cbd1a4

2026-09-01 [email protected] Roll Skia from 6be7f8547c3c to 3911a1fe7f7a (1 revision) (flutter/flutter#192110)
2026-09-01 [email protected] Roll Fuchsia Linux SDK from EPzxifoyt36b5qiBy... to idslm9FikVLy2K_A-... (flutter/flutter#192104)
2026-09-01 [email protected] Roll Skia from 47df2ae3226c to 6be7f8547c3c (1 revision) (flutter/flutter#192103)
2026-09-01 [email protected] Roll Skia from e22ebf131e44 to 47df2ae3226c (4 revisions) (flutter/flutter#192099)
2026-09-01 [email protected] Roll Skia from 5489a16a5998 to e22ebf131e44 (5 revisions) (flutter/flutter#192093)
2026-09-01 [email protected] [flutter_tools] Safely handle broken symlinks and existing files during plugin symlink creation (flutter/flutter#191496)
2026-09-01 [email protected] Support wide gamut colors in gradient_generator's CreateGradientTexture (flutter/flutter#191980)
2026-09-01 [email protected] Clean up semantics code (flutter/flutter#191620)
2026-08-31 [email protected] Add website documentation item to PR pre-launch checklist (flutter/flutter#192080)
2026-08-31 [email protected] [windows] Fix null-deref in HostWindowPopup::UpdatePosition (Fixes #191478) (flutter/flutter#191479)
2026-08-31 [email protected] Removes deprecated ignoreSemantics parementers (flutter/flutter#191493)
2026-08-31 [email protected] Roll Dart SDK from b319095e317b to 9164def35347 (1 revision) (flutter/flutter#192073)
2026-08-31 [email protected] test: dynamic _tapOutside helper for TapRegion navigation tests (flutter/flutter#185397)
2026-08-31 [email protected] [Windows] fix: Remove quotes from compiler warning suppression (flutter/flutter#190873)
2026-08-31 [email protected] Migrate to listen package (flutter/flutter#189111)
2026-08-31 [email protected] Roll Skia from 15db98a90bbd to 5489a16a5998 (2 revisions) (flutter/flutter#192070)
2026-08-31 [email protected] Add regression test for plugin compileSdkExtension warning (flutter/flutter#191281)
2026-08-31 [email protected] Explicitly disable HCPP in platform view benchmarks and integration tests (flutter/flutter#191908)
2026-08-31 [email protected] Documentation and script for Gradle Distribution cache for CI (flutter/flutter#190323)
2026-08-31 [email protected] Roll Skia from 5549c93c9a1c to 15db98a90bbd (2 revisions) (flutter/flutter#192068)
2026-08-31 [email protected] Roll Packages from cd4cdd0 to d642322 (7 revisions) (flutter/flutter#192063)
2026-08-31 [email protected] Roll Dart SDK from 48f641e8b249 to b319095e317b (1 revision) (flutter/flutter#192061)
2026-08-31 [email protected] [tool] Migrate dev/tools from dart_skills_lint to skills_lint package (flutter/flutter#191997)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
victorsanni pushed a commit to victorsanni/packages that referenced this pull request Sep 9, 2026
…r#12712)

flutter/flutter@c8c5e3b...0cbd1a4

2026-09-01 [email protected] Roll Skia from 6be7f8547c3c to 3911a1fe7f7a (1 revision) (flutter/flutter#192110)
2026-09-01 [email protected] Roll Fuchsia Linux SDK from EPzxifoyt36b5qiBy... to idslm9FikVLy2K_A-... (flutter/flutter#192104)
2026-09-01 [email protected] Roll Skia from 47df2ae3226c to 6be7f8547c3c (1 revision) (flutter/flutter#192103)
2026-09-01 [email protected] Roll Skia from e22ebf131e44 to 47df2ae3226c (4 revisions) (flutter/flutter#192099)
2026-09-01 [email protected] Roll Skia from 5489a16a5998 to e22ebf131e44 (5 revisions) (flutter/flutter#192093)
2026-09-01 [email protected] [flutter_tools] Safely handle broken symlinks and existing files during plugin symlink creation (flutter/flutter#191496)
2026-09-01 [email protected] Support wide gamut colors in gradient_generator's CreateGradientTexture (flutter/flutter#191980)
2026-09-01 [email protected] Clean up semantics code (flutter/flutter#191620)
2026-08-31 [email protected] Add website documentation item to PR pre-launch checklist (flutter/flutter#192080)
2026-08-31 [email protected] [windows] Fix null-deref in HostWindowPopup::UpdatePosition (Fixes #191478) (flutter/flutter#191479)
2026-08-31 [email protected] Removes deprecated ignoreSemantics parementers (flutter/flutter#191493)
2026-08-31 [email protected] Roll Dart SDK from b319095e317b to 9164def35347 (1 revision) (flutter/flutter#192073)
2026-08-31 [email protected] test: dynamic _tapOutside helper for TapRegion navigation tests (flutter/flutter#185397)
2026-08-31 [email protected] [Windows] fix: Remove quotes from compiler warning suppression (flutter/flutter#190873)
2026-08-31 [email protected] Migrate to listen package (flutter/flutter#189111)
2026-08-31 [email protected] Roll Skia from 15db98a90bbd to 5489a16a5998 (2 revisions) (flutter/flutter#192070)
2026-08-31 [email protected] Add regression test for plugin compileSdkExtension warning (flutter/flutter#191281)
2026-08-31 [email protected] Explicitly disable HCPP in platform view benchmarks and integration tests (flutter/flutter#191908)
2026-08-31 [email protected] Documentation and script for Gradle Distribution cache for CI (flutter/flutter#190323)
2026-08-31 [email protected] Roll Skia from 5549c93c9a1c to 15db98a90bbd (2 revisions) (flutter/flutter#192068)
2026-08-31 [email protected] Roll Packages from cd4cdd0 to d642322 (7 revisions) (flutter/flutter#192063)
2026-08-31 [email protected] Roll Dart SDK from 48f641e8b249 to b319095e317b (1 revision) (flutter/flutter#192061)
2026-08-31 [email protected] [tool] Migrate dev/tools from dart_skills_lint to skills_lint package (flutter/flutter#191997)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
victorsanni pushed a commit to victorsanni/packages that referenced this pull request Sep 9, 2026
…r#12712)

flutter/flutter@c8c5e3b...0cbd1a4

2026-09-01 [email protected] Roll Skia from 6be7f8547c3c to 3911a1fe7f7a (1 revision) (flutter/flutter#192110)
2026-09-01 [email protected] Roll Fuchsia Linux SDK from EPzxifoyt36b5qiBy... to idslm9FikVLy2K_A-... (flutter/flutter#192104)
2026-09-01 [email protected] Roll Skia from 47df2ae3226c to 6be7f8547c3c (1 revision) (flutter/flutter#192103)
2026-09-01 [email protected] Roll Skia from e22ebf131e44 to 47df2ae3226c (4 revisions) (flutter/flutter#192099)
2026-09-01 [email protected] Roll Skia from 5489a16a5998 to e22ebf131e44 (5 revisions) (flutter/flutter#192093)
2026-09-01 [email protected] [flutter_tools] Safely handle broken symlinks and existing files during plugin symlink creation (flutter/flutter#191496)
2026-09-01 [email protected] Support wide gamut colors in gradient_generator's CreateGradientTexture (flutter/flutter#191980)
2026-09-01 [email protected] Clean up semantics code (flutter/flutter#191620)
2026-08-31 [email protected] Add website documentation item to PR pre-launch checklist (flutter/flutter#192080)
2026-08-31 [email protected] [windows] Fix null-deref in HostWindowPopup::UpdatePosition (Fixes #191478) (flutter/flutter#191479)
2026-08-31 [email protected] Removes deprecated ignoreSemantics parementers (flutter/flutter#191493)
2026-08-31 [email protected] Roll Dart SDK from b319095e317b to 9164def35347 (1 revision) (flutter/flutter#192073)
2026-08-31 [email protected] test: dynamic _tapOutside helper for TapRegion navigation tests (flutter/flutter#185397)
2026-08-31 [email protected] [Windows] fix: Remove quotes from compiler warning suppression (flutter/flutter#190873)
2026-08-31 [email protected] Migrate to listen package (flutter/flutter#189111)
2026-08-31 [email protected] Roll Skia from 15db98a90bbd to 5489a16a5998 (2 revisions) (flutter/flutter#192070)
2026-08-31 [email protected] Add regression test for plugin compileSdkExtension warning (flutter/flutter#191281)
2026-08-31 [email protected] Explicitly disable HCPP in platform view benchmarks and integration tests (flutter/flutter#191908)
2026-08-31 [email protected] Documentation and script for Gradle Distribution cache for CI (flutter/flutter#190323)
2026-08-31 [email protected] Roll Skia from 5549c93c9a1c to 15db98a90bbd (2 revisions) (flutter/flutter#192068)
2026-08-31 [email protected] Roll Packages from cd4cdd0 to d642322 (7 revisions) (flutter/flutter#192063)
2026-08-31 [email protected] Roll Dart SDK from 48f641e8b249 to b319095e317b (1 revision) (flutter/flutter#192061)
2026-08-31 [email protected] [tool] Migrate dev/tools from dart_skills_lint to skills_lint package (flutter/flutter#191997)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[tool_crash] FileSystemException: Creation failed, OS Error: A file cannot be created if it already exists., errno = 183

2 participants