Repository navigation
Description
Steps to reproduce
On one machine, in one checkout/workspace (a persistent CI workspace or a developer machine switching branches):
- Check out branch A whose dependency graph contains a package with a native-assets build hook (e.g.
cupertino_http3.x, or any package withhook/build.dartproducing aCodeAsset). Build the app (e.g.flutter build ios --simulator). The hook output (e.g.libcupertino_http.dylib) is written to.dart_tool/hooks_runner/shared/<package>/build/<config>/. - Check out branch B whose dependency graph does not contain that package (or contains a version without a hook, e.g.
cupertino_http2.x). Build the app again with the same mode/flavor/target. - Check out branch A again and build.
Expected results
Step 3 rebuilds (or reuses) the hook outputs and succeeds.
Actual results
Step 3 fails in install_code_assets:
Target install_code_assets failed: Error: Failed to create universal binary:
fatal error: .../usr/bin/lipo: can't open input file: <workspace>/.dart_tool/hooks_runner/shared/cupertino_http/build/84fd4fa0ea/libcupertino_http.dylib (No such file or directory)
(on Android the equivalent is Flutter failed to copy file from ".../hooks_runner/shared/pdfium_dart/build/.../libpdfium.so" ... The file or directory could not be found).
The failure persists on every subsequent build of branch A in that workspace until .dart_tool/hooks_runner is deleted manually.
Root cause
Two caching subsystems disagree about who owns the shared hook artifacts:
- The
dart_buildtarget declares the built hook artifacts (result.filesToBeBundled, which live under.dart_tool/hooks_runner/shared/) as its depfile outputs —packages/flutter_tools/lib/src/build_system/targets/native_assets.dart(Depfileconstruction inDartBuild.build). - The build system's stale-output cleanup —
packages/flutter_tools/lib/src/build_system/build_system.dart, "Delete outputs from previous stages that are no longer a part of the build" (ErrorHandlingFileSystem.deleteIfExists(previousFile)) — compares the previous stamp's outputs with the current build's outputs. The flutter_build config directory hash is derived from mode/target/defines, not from the resolved package graph, so branch B's build reuses branch A's stamp, sees the dylib inpreviousOutputsbut not in its own outputs (no hook packages), and deletes it from the shared hooks_runner directory. hooks_runner's own skip check (hooks_runner-1.1.1,lib/src/build_runner/build_runner.dart, theSkipping build for <package>path) validatesinput.jsonequality,output.jsonexistence and input dependency hashes, but never checks that the output artifacts declared inoutput.jsonstill exist on disk. So in step 3 the hook build is skipped against intact metadata, andinstall_code_assetsthen fails on the deleted file.
We confirmed this in CI forensically: the shared dylib's parent directory mtime (the deletion moment) matched to the second the flutter assemble phase of an intervening build of a branch without the hook packages. Only the binary artifacts were deleted; the hooks_runner metadata JSONs survived, which is exactly why the next build skipped the rebuild.
Either side would fix it:
- exclude
.dart_tool/hooks_runner/shared/paths from stale-output deletion (there is already apreservedOutputFilePathsmechanism used for a similar SwiftPM-owned-output case), or - make hooks_runner's skip check verify that declared output files still exist (dart-lang/native).
This hits any CI fleet with persistent workspaces where branches with different dependency graphs alternate (for us: a GitHub merge queue — the PR introducing the hook packages failed repeatedly and was repeatedly ejected), and any developer switching between such branches locally.
Flutter Doctor output
Doctor output
Flutter 3.44.4 • channel stable • https://github.com/flutter/flutter.git
Framework • revision ad70ec4617 • 2026-06-24
Engine • revision a10d8ac38d
Tools • Dart 3.12.2
(observed on macOS 26 hosts, iOS simulator and Android targets; hooks_runner 1.1.1)
Activity
mbcorona commentedon Aug 12, 2026
Thanks for the report, routing to the tool team for evaluation.
dcharkes commentedon Aug 18, 2026
Right, hooks_runner does not check if output files are not deleted and flutter_tool does check if output files are deleted.
[flutter_tools] Prevent deletion of shared native asset hooks outputs…
github-actions commentedon Sep 3, 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.