skip to content

In a Flutter app's CI, what does `dart pub get --enforce-lockfile` guarantee that plain `dart pub get` does not, and should the app commit pubspec.lock?

level: middleimportance: should knowfreq 40%

answer

  1. apps commit, packages don't
  2. get may silently unlock
  3. three unlock triggers
  4. content hash mismatch fails
  5. production deploys

basics

~10 s

Plain pub get quietly unlocks versions when the pubspec changed, a locked version vanished or the SDK changed, and it rewrites mismatched content hashes. --enforce-lockfile fails instead. Apps commit pubspec.lock; regular packages do not.

solid answer

~40 s

An **application** package should commit `pubspec.lock`, so every developer and every deploy uses the same versions and transitive changes show up in diffs; a **regular** package should not, so its tests keep running against the latest compatible versions. Even with a committed lockfile, plain `dart pub get` may deviate from it: it **unlocks** enough versions to resolve when dependencies were added or removed, when a locked version no longer exists, or when the SDK changed. If a hosted package's **content hash** differs, it warns and updates the lockfile. `dart pub get --enforce-lockfile` turns all of that into a **failure**: the lockfile must exactly specify a valid resolution of the pubspec, and every hash must match. That is why dart.dev recommends it for CI and production deploys.

code

bash · 7 lines
bash
# CI / release build of an app with a committed pubspec.lock
dart pub get --enforce-lockfile
dart analyze
dart test

# Typical failure after someone edited pubspec.yaml without re-resolving:
# the command exits non-zero instead of rewriting pubspec.lock

go deeper

for a junior

Know the rule: commit pubspec.lock for apps, not for packages, and never commit .dart_tool.

for a middle

Explain the three cases where plain pub get unlocks versions and what --enforce-lockfile turns into failures.

for a senior

Wire --enforce-lockfile into CI and release builds, and make lockfile diffs part of code review.

for a principal

Decide how reproducibility and upgrade freshness are split between app pipelines and shared-package pipelines across teams.

## Commit policy on dart.dev Pub's guidance distinguishes two kinds of package: - **Application packages** (apps, servers, command-line tools that nobody depends on): **commit `pubspec.lock`**. Every developer, every CI run and every deploy then uses the exact same versions of all dependencies, and each change to a transitive dependency, whether from `dart pub upgrade` or a pubspec edit, is **visible in the lockfile diff**. - **Regular packages** (libraries others depend on): **don't commit** it. A consumer never uses your lockfile anyway, and regenerating it lets your own CI test against the latest compatible versions of your dependencies. `.dart_tool/`, which holds `package_config.json`, is never committed. ## When plain `dart pub get` does not follow the lockfile A committed lockfile is not a hard guarantee on its own. dart.dev lists three cases where `dart pub get` does **not** retrieve the exact locked versions: 1. dependencies were **added to or removed from** `pubspec.yaml` after the lockfile was last written; 2. a locked version **no longer exists** in the package repository; 3. you changed to a **different Dart SDK**, and some locked packages are not compatible with it. In each case pub **unlocks just enough** versions to find a resolution and reports the changes, then writes the new lockfile. Separately, if the **content hash** of a published version differs from the one recorded in `pubspec.lock`, pub warns that the content changed on the server or the lockfile is corrupted, and **updates the hash**. On a developer's laptop that is convenient. In CI, or on the machine that builds a release, it means the artifact can contain versions nobody tested. ## What `--enforce-lockfile` changes `dart pub get --enforce-lockfile` makes the lockfile **authoritative**. It fails with an error if: - `pubspec.lock` does not **exactly specify a valid resolution** of `pubspec.yaml` (including a missing lockfile, or a pubspec edited without re-resolving); - any **content hash** of a hosted package has changed. It does not modify the lockfile. dart.dev calls it useful for CI and for deploying to production, because it avoids shipping untested dependencies and detects altered package content. | Situation | plain `dart pub get` | `--enforce-lockfile` | |---|---|---| | pubspec changed, lockfile stale | re-resolves, rewrites lockfile | fails | | locked version deleted upstream | picks another version | fails | | content hash differs | warns, updates hash | fails | | lockfile matches | uses it | uses it | ## A pipeline that uses it 1. Developers run `dart pub get` or `dart pub upgrade` locally and commit both `pubspec.yaml` and `pubspec.lock`. 2. Code review reads the lockfile diff for unexpected transitive moves. 3. CI and release builds run `dart pub get --enforce-lockfile` (in a Flutter app, through the Flutter tool's pub wrapper), so a forgotten lockfile update fails the build instead of resolving silently. 4. Separately, a scheduled job can run `dart pub outdated` and open upgrade changes. For a **regular package**, CI takes the opposite approach: run a fresh `dart pub get`, and also `dart pub downgrade`, to test both ends of the ranges the pubspec claims.

  • In a Dart app's CI, `dart pub get --enforce-lockfile` fails right after a teammate added a dependency. What happened, and what is the fix?
    The teammate edited `pubspec.yaml` but did not commit the updated `pubspec.lock`, so the lockfile no longer describes a valid resolution of the pubspec. Run `dart pub get` locally, review the lockfile diff and commit it; do not drop the flag.
  • Why doesn't dart.dev recommend committing pubspec.lock for a regular Dart package?
    Consumers resolve the package inside their own graph and never read its lockfile. Leaving it uncommitted means each fresh `dart pub get` in the package's CI tests against the latest compatible versions, which is what users will actually get.

saying these in an interview costs you the question

  • A committed pubspec.lock alone guarantees identical versions everywhere
  • --enforce-lockfile upgrades packages whose hashes changed
  • Regular packages should commit pubspec.lock so consumers get the same versions
  • A content-hash mismatch is ignored by plain pub get
  • .dart_tool/package_config.json should be committed with the lockfile