Skip to content

Native assets: build of a branch without hook packages deletes shared hook outputs as stale; next build skips the hook and fails in install_code_assets #191020

Closed

Description

@btrautmann

Steps to reproduce

On one machine, in one checkout/workspace (a persistent CI workspace or a developer machine switching branches):

  1. Check out branch A whose dependency graph contains a package with a native-assets build hook (e.g. cupertino_http 3.x, or any package with hook/build.dart producing a CodeAsset). 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>/.
  2. Check out branch B whose dependency graph does not contain that package (or contains a version without a hook, e.g. cupertino_http 2.x). Build the app again with the same mode/flavor/target.
  3. 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:

  1. The dart_build target 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 (Depfile construction in DartBuild.build).
  2. 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 in previousOutputs but not in its own outputs (no hook packages), and deletes it from the shared hooks_runner directory.
  3. hooks_runner's own skip check (hooks_runner-1.1.1, lib/src/build_runner/build_runner.dart, the Skipping build for <package> path) validates input.json equality, output.json existence and input dependency hashes, but never checks that the output artifacts declared in output.json still exist on disk. So in step 3 the hook build is skipped against intact metadata, and install_code_assets then 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 a preservedOutputFilePaths mechanism 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

  1. added
    in triagePresently being triaged by the triage team
    toolAffects the "flutter" command-line tool. See also t: labels.
    team-toolOwned by Flutter Tool team
    has reproducible stepsThe issue has been confirmed reproducible and is ready to work on
    and removed
    in triagePresently being triaged by the triage team
    on Aug 12, 2026
  2. mbcorona commented on Aug 12, 2026

    @mbcorona
    Member

    Thanks for the report, routing to the tool team for evaluation.

  3. self-assigned this
    on Aug 18, 2026
  4. added
    P2Important issues not at the top of the work list
    triaged-toolTriaged by Flutter Tool team
    on Aug 18, 2026
  5. dcharkes commented on Aug 18, 2026

    @dcharkes
    Contributor

    Right, hooks_runner does not check if output files are not deleted and flutter_tool does check if output files are deleted.

  6. github-actions commented on Sep 3, 2026

    @github-actions

    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 -v and a minimal reproduction of the issue.

  7. locked as resolved and limited conversation to collaborators on Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P2Important issues not at the top of the work lista: assetsPackaging, accessing, or using assetshas reproducible stepsThe issue has been confirmed reproducible and is ready to work onteam-toolOwned by Flutter Tool teamtoolAffects the "flutter" command-line tool. See also t: labels.triaged-toolTriaged by Flutter Tool team

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions