Keep `flutter pub get` From Rewriting the Lockfile in CI
CI should resolve the dependency graph recorded by a committed Dart lockfile, not silently create a different one. Use the right command for applications and packages.
Applications and libraries have different lockfile expectations
For an application, pubspec.lock normally belongs in version control because the shipped app should be built from a reviewed dependency resolution. In CI, flutter pub get --enforce-lockfile asks Pub to fail when the lockfile would need to change. This catches a stale or inconsistent dependency state instead of updating it as a side effect.
flutter pub get --enforce-lockfile
git diff --exit-code -- pubspec.lockThe second check is useful when the CI command or a plugin can still touch files. A clean diff proves the checkout was not altered; it does not prove the dependency versions are secure or compatible.
Reusable packages have a different release and resolution workflow. Their lockfile policy may differ from an application, so follow the package's supported Dart and Flutter constraints instead of blindly applying an app rule.
Diagnose a lockfile mismatch without deleting it
If enforced resolution fails, inspect the declared SDK constraints, dependency overrides, and the exact lockfile diff from the branch that introduced the change. Run flutter pub get locally only when intentionally updating dependencies, inspect the resulting diff, test the supported SDK range, and commit the lockfile alongside the manifest change.
Do not remove pubspec.lock to “fix” a solver error. That discards the graph and can select versions unrelated to the failing package. Also avoid running dependency resolution under a different Flutter channel or Dart SDK than CI; print flutter --version at the start of both environments.
The useful outcome is a reproducible resolution with a reviewed diff. If Pub reports incompatible constraints, identify the package that introduced the conflict and change the smallest necessary constraint rather than upgrading every dependency at once.
Make the lockfile check match the repository's ownership model
Before enforcing a lockfile, inspect what kind of package the repository ships. A Flutter application is an executable product with a concrete dependency graph, so committing pubspec.lock gives developers and CI a shared resolution. A reusable package is consumed by other applications across a range of dependency versions, and its lockfile is generally not the contract for downstream users. A monorepo can contain both kinds of project; set the policy per package instead of applying a single root-level rule to every directory.
The command should run from the package directory that owns the manifest. In a workspace, running Pub from the repository root may target the wrong package or leave some package lockfiles unchecked. Have CI enumerate the intended application directories and print each one before resolving. Pin or otherwise control the Flutter SDK version as well, because a lockfile generated under one SDK and checked under another can reveal a legitimate solver difference.
Review a lockfile change as a dependency change
When the enforced check fails, reproduce the resolution locally with the same Flutter and Dart versions as CI. Inspect the manifest and lockfile together. Look for an intentional dependency addition, a changed SDK constraint, an override, or a package whose published versions no longer satisfy the declared range. A solver message identifies constraints, but the fix should still be reviewed for compatibility with the app's supported platform and SDK matrix.
Do not automatically run an unconstrained update in CI to make the job green. That would let a build produce a new dependency graph without a reviewed source change, which weakens reproducibility and can make two builds from the same commit differ. Instead, update dependencies in a dedicated change, run tests, inspect transitive updates, and commit the exact resulting lockfile. If a lockfile is intentionally generated during CI for a library workflow, make that behavior explicit and avoid presenting it as a locked application build.
Finally, preserve the failure output as an artifact when a resolution check fails. Include the tool versions and the lockfile diff, but avoid collecting unrelated machine secrets from the job environment. A useful failure tells the maintainer which package and constraint caused drift and points to the owning manifest. It should not invite a broad cache purge or a blind lockfile deletion.
Make the lockfile check match the repository's ownership model
Before enforcing a lockfile, inspect what kind of package the repository ships. A Flutter application is an executable product with a concrete dependency graph, so committing pubspec.lock gives developers and CI a shared resolution. A reusable package is consumed by other applications across a range of dependency versions, and its lockfile is generally not the contract for downstream users. A monorepo can contain both kinds of project; set the policy per package instead of applying a single root-level rule to every directory.
The command should run from the package directory that owns the manifest. In a workspace, running Pub from the repository root may target the wrong package or leave some package lockfiles unchecked. Have CI enumerate the intended application directories and print each one before resolving. Pin or otherwise control the Flutter SDK version as well, because a lockfile generated under one SDK and checked under another can reveal a legitimate solver difference.
Review a lockfile change as a dependency change
When the enforced check fails, reproduce the resolution locally with the same Flutter and Dart versions as CI. Inspect the manifest and lockfile together. Look for an intentional dependency addition, a changed SDK constraint, an override, or a package whose published versions no longer satisfy the declared range. A solver message identifies constraints, but the fix should still be reviewed for compatibility with the app's supported platform and SDK matrix.
Do not automatically run an unconstrained update in CI to make the job green. That would let a build produce a new dependency graph without a reviewed source change, which weakens reproducibility and can make two builds from the same commit differ. Instead, update dependencies in a dedicated change, run tests, inspect transitive updates, and commit the exact resulting lockfile. If a lockfile is intentionally generated during CI for a library workflow, make that behavior explicit and avoid presenting it as a locked application build.
Finally, preserve the failure output as an artifact when a resolution check fails. Include the tool versions and the lockfile diff, but avoid collecting unrelated machine secrets from the job environment. A useful failure tells the maintainer which package and constraint caused drift and points to the owning manifest. It should not invite a broad cache purge or a blind lockfile deletion.