Terraform `init` fails on a Linux CI runner with a checksum error against a `.terraform.lock.hcl` generated on a developer's Mac. What causes this, and what is the durable fix?
answer
- one package per OS and architecture
- local hashes describe one build only
- the lock is missing coverage, not corrupt
- a subcommand records extra platforms
- deleting the file removes the guarantee
basics
~20 sThe lock file records checksums for provider packages it has actually seen. If it only carries hashes for the developer's platform, a runner on a different OS or architecture downloads a package matching nothing recorded and init aborts. Fix it with terraform providers lock -platform=... for every platform in use.
solid answer
~40 sProvider packages are per-platform builds, and the `h1:` hashes in `.terraform.lock.hcl` correspond to packages Terraform actually installed. When a lock file carries hashes only for `darwin_arm64`, a `linux_amd64` runner fetches a package whose checksum matches nothing recorded, and Terraform refuses to proceed — correctly, since it cannot distinguish an unrecorded platform from a tampered binary. It commonly happens when providers come from a mirror or private registry, or when the plugin cache directory means Terraform never saw the registry's signed checksum document. The durable fix is to record every platform the team uses: `terraform providers lock -platform=linux_amd64 -platform=darwin_arm64 -platform=darwin_amd64`, then commit the enriched lock file. Deleting the lock file in CI "fixes" it by throwing away both the pin and the integrity check.
code
bash · 11 lines# Record checksums for every platform the team and CI use,
# then commit the enriched lock file.
terraform providers lock \
-platform=linux_amd64 \
-platform=darwin_arm64 \
-platform=darwin_amd64
git add .terraform.lock.hcl
# Guard: make CI fail instead of silently rewriting the lock file.
terraform init -lockfile=readonlygo deeper
Know that provider binaries differ per operating system and CPU architecture, and that the lock file records checksums for the packages it has seen rather than for every platform automatically.
Explain the difference between the registry's signed checksums and the locally computed per-platform hashes, and name terraform providers lock -platform=... as the command that adds coverage.
Diagnose it end to end: confirm the missing platform coverage, work out how the lock file was produced (mirror, private registry, plugin cache), fix it once, commit it, and refuse the delete-the-lock-file shortcut with a clear reason.
Own the provider supply chain — where binaries come from, whether a mirror is mandated, which platforms are supported estate-wide, and what the response is when a checksum fails on a platform that was already recorded.
## Why a lock file can be platform-specific A provider release is not one artifact — it is one package per OS/architecture pair: `linux_amd64`, `linux_arm64`, `darwin_amd64`, `darwin_arm64`, `windows_amd64`. The lock file holds two kinds of hash for a provider entry. `zh:` entries come from the registry's signed checksum document published with the release and describe the release's packages generally, so a lock file containing them can verify an install on a platform that machine never ran. `h1:` entries are computed by Terraform from a package it actually installed, so each one is tied to one platform build. A lock file whose entry has good `zh:` coverage travels fine between a Mac and a Linux runner. A lock file carrying only the `h1:` hash for the machine that generated it does not: Terraform downloads the `linux_amd64` package, computes its hash, finds nothing matching in the recorded list, and stops with a message that the package does not match any of the checksums recorded in the dependency lock file. ## Why the signed checksums are sometimes missing This is the part worth being precise about, because a straightforward install from the public registry usually records the signed checksums and this problem never appears. It shows up when Terraform installed the provider **without** seeing that document: - a **filesystem or network mirror** (`-plugin-dir`, a `provider_installation` mirror block, or output of `terraform providers mirror`) serving repackaged binaries; - a **private registry or proxy** that does not publish the signed checksum file; - the **provider plugin cache directory**, where Terraform can install from the local cache and end up recording only the locally computed hash; - a lock file generated by an older toolchain or hand-edited during a merge-conflict resolution. So the honest diagnosis in an interview is two-step: confirm the failing entry's hash list is missing coverage for the runner's platform, and then ask *how* that lock file was produced, because the answer usually names a mirror or a cache. ## The durable fix ```bash terraform providers lock \ -platform=linux_amd64 \ -platform=darwin_arm64 \ -platform=darwin_amd64 ``` `terraform providers lock` exists for exactly this: it consults the configured installation sources for each named platform, records checksums for all of them in `.terraform.lock.hcl`, and leaves you a lock file that verifies everywhere. Run it once, commit the result, and re-run it when you add a platform — an Apple-silicon laptop, an arm64 runner — or when you bump provider versions through a mirror. It also accepts `-fs-mirror` and `-net-mirror` when the packages come from somewhere other than the default source. Make it repeatable rather than folklore: a short `make providers-lock` target or a documented step in the upgrade runbook, so the next person who bumps a provider does not rediscover the failure from a red pipeline. ## The fixes that make things worse **Deleting `.terraform.lock.hcl` in the CI step** is the reflex to argue against. It makes the error disappear because there is nothing left to verify against — and simultaneously unpins the version, so the pipeline now installs whatever the registry currently offers and its supply-chain check is gone. **Adding `-upgrade`** is nearly as bad: it papers over the platform gap by re-resolving and re-recording for the runner's platform, moving the provider version as a side effect and possibly flipping the failure to the developer's Mac next time. **Adding `.terraform.lock.hcl` to `.gitignore`** ends the argument permanently by discarding reproducibility altogether. A related, benign case worth recognising so it is not mistaken for this one: when a colleague on a new platform runs `init` and the lock file simply *gains* an `h1:` line for that platform, that one-line diff is normal enrichment, not corruption. Commit it. ## What to say about tampering Terraform cannot tell an unrecorded platform apart from a modified binary, which is why the failure is hard and not a warning. When a checksum mismatch appears on a platform you *have* recorded, with no mirror in the path and no version change, stop treating it as a platform gap and treat it as a supply-chain question: check whether an intermediary is repackaging the download before you overwrite anything.
- A colleague's init adds one h1: line to the lock file with no version change. Is that a problem?No — that is the normal enrichment case. They installed the same provider version on a platform the lock file had not seen, so Terraform appended that platform's hash. Commit it; the lock file is now valid for one more platform. It is only worth investigating if the *version* changed, or if a hash for an already-recorded platform changed, which is a different and more serious signal.
- Why is deleting the lock file in the CI step the wrong fix?It removes both guarantees at once. The version pin goes, so the pipeline installs whatever the registry currently offers and identical commits can run different plugins; and the checksum verification goes, so a repackaged or tampered binary would be installed without complaint. It converts a loud, specific failure into a silent loss of reproducibility and supply-chain integrity.
- When should a checksum mismatch be treated as a security event rather than a platform gap?When the platform is already recorded, the provider version has not changed, and nothing in the installation path was altered — then the package genuinely differs from what was verified before. Investigate whether a mirror, proxy or registry in the path is repackaging the download before overwriting anything, and preserve the failing lock file and logs rather than regenerating over them.
saying these in an interview costs you the question
- Deleting the lock file so CI init passes
- Assuming a lock file is inherently platform-independent
- Adding -upgrade to paper over a checksum mismatch
- Blaming the registry rather than missing platform coverage
- Treating every checksum failure as an attack, or none of them