Terraform 1.16.3 shipped on 16 September 2026, and within hours every CI job that fetches the CLI through HashiCorp's own Go installer started refusing it. This takes you from the error string to a running plan in about ten minutes, and leaves you with a one-line test that tells you when the workaround can come out.

TL;DR: The archive is not corrupt. releases.hashicorp.com serves terraform_1.16.3_*.zip with content-type: binary/octet-stream, and hc-install only accepts application/zip or application/x-zip-compressed, so it aborts before it unpacks anything. curl, tfenv and tfswitch never read that header, so the same URL works by hand. Pin != 1.16.3 or hand your consumer a binary it already has, then watch the header for the server-side fix.

The failure looks like this, from Atlantis, from provider acceptance tests, from anything embedding hashicorp/hc-install:

error downloading terraform version 1.16.3: unexpected content-type: binary/octet-stream (expected any of ["application/x-zip-compressed" "application/zip"])

Three different problems print almost that same sentence, and the open threads run them together: a release-side metadata regression from last week, a 2022-era check at a completely different stage of the install, and a corporate proxy rewriting the header in transit. hashicorp/terraform#39235 and hashicorp/hc-install#407 were both filed on 16 September 2026 and, at the time of writing, neither carries a maintainer fix. Any model answering from training data older than that will call it a corrupt download or a bad mirror and send you to clear caches. One warning summary with several unrelated triggers is the same diagnostic trap as Terraform 1.15's deprecation-mark warnings: you cannot pick a fix until you know which trigger you have.

Prerequisites

  • curl 7.x or later with outbound access to releases.hashicorp.com, plus sha256sum and unzip.
  • One affected consumer to reproduce against: Atlantis v0.44.0 or similar, a provider repo whose acceptance tests use terraform-plugin-testing, or a Go program importing hc-install.
  • A known-good binary to fall back to. 1.16.2 is unaffected, and a patch-level rollback is cheap here, unlike the version-specific state behaviour behind the deposed-object panic in readDiff.
  • Shell access on the host where the download actually runs. Reproducing on a laptop will not reproduce the proxy variant, which is the point of step 2.

What the three threads actually say: #39235 is the Terraform-side report with the confirmed-good checksum and the != 1.16.3 workaround; hc-install#407 argues the library should accept the generic type because checksum and signature verification happen after the download anyway; runatlantis/atlantis#6906 is the same break surfacing as failed plans on pull requests.

Step-by-step

1. Read which stage of the install failed

hc-install validates content types in two separate places and the messages differ by one substring. A message naming application/vnd+hashicorp.releases-api.v0+json comes from the releases-index stage in releasesjson/releases.go, which is the 2022 problem from hc-install issues #56 and #59, relaxed by PR #57. A message naming binary/octet-stream comes from the archive stage in internal/releasesjson/downloader.go, inside DownloadAndUnpack(), which compares the response header against a two-entry list:

var zipMimeTypes = []string{
	"application/x-zip-compressed", // Windows
	"application/zip",              // Unix
}

Only the second check is live today. Sorting this out first keeps you from applying a four-year-old library upgrade to a two-day-old server-side break.

2. Ask the origin what it is serving, and compare two versions

curl -sSIL https://releases.hashicorp.com/terraform/1.16.3/terraform_1.16.3_linux_amd64.zip | grep -i '^content-type'
curl -sSIL https://releases.hashicorp.com/terraform/1.16.2/terraform_1.16.2_linux_amd64.zip | grep -i '^content-type'

Expect content-type: binary/octet-stream on the first line and content-type: application/zip on the second. The 1.16.3 object did not get the per-object metadata that every previous release got. Run this from the failing host rather than your workstation: a header rewritten by an intercepting proxy or an Artifactory remote repository only shows up on the path CI takes. If 1.16.3 already returns application/zip for you, HashiCorp has re-tagged the object and whatever you are still seeing is cached or proxy-side.

3. Prove the bytes are good before you touch anything else

curl -sSLO https://releases.hashicorp.com/terraform/1.16.3/terraform_1.16.3_linux_amd64.zip
curl -sSL https://releases.hashicorp.com/terraform/1.16.3/terraform_1.16.3_SHA256SUMS \
  | grep linux_amd64 | sha256sum -c -

Expected output: terraform_1.16.3_linux_amd64.zip: OK. The reporter on #39235 did the same check against terraform_1.16.3_darwin_arm64.zip and got a valid zip with a matching SHA256. Two things follow. You can stop clearing caches, and you now have the argument that makes a correct fix stick: hc-install verifies checksum and signature after the download, so the header it rejected was never the integrity control. Trusting an artifact by its checksum instead of by transport metadata is the same discipline that makes SHA-pinned GitHub Actions worth the friction.

4. Apply the fix that matches your consumer

Atlantis resolves a Terraform version from PATH and its own binary directory before it reaches out to releases.hashicorp.com, and per the server configuration reference --tf-download=false stops the outbound attempt entirely:

atlantis server --tf-download=false --default-tf-version=v1.16.2

Provider acceptance tests take the binary from an environment variable instead. HashiCorp's acceptance testing docs describe TF_ACC_TERRAFORM_PATH as the path to an existing binary; leaving both it and TF_ACC_TERRAFORM_VERSION unset is what makes the harness install the latest CLI into a temp directory on every run:

export TF_ACC_TERRAFORM_PATH="$(command -v terraform)"
TF_ACC=1 go test ./internal/provider/... -v

One trap is documented in that same page and costs people an afternoon: if TF_ACC_TERRAFORM_PATH points at something missing or non-executable and TF_ACC_TERRAFORM_VERSION is set, the harness installs anyway and the error comes straight back. Set the path, then confirm the binary is there.

For a Go program of your own, change the install source so it locates a binary rather than fetching one:

execPath, err := install.NewInstaller().Ensure(ctx, []src.Source{
	&fs.AnyVersion{Product: &product.Terraform}, // was: &releases.LatestVersion{Product: product.Terraform}
})

Ensure() walks the slice and falls through to the next source, so leaving the releases entry in the list puts the download straight back on the critical path.

5. Pin around the release where a constraint drives the download

terraform {
  required_version = ">= 1.14.0, != 1.16.3"
}

This is the workaround posted on #39235 and it resolves to 1.16.2. Be clear with your team about what it does not cover: it constrains version selection, so it helps tooling that reads the constraint and does nothing for a pipeline with 1.16.3 hardcoded in a Dockerfile, a setup-terraform step, or a build matrix.

6. Keep the decision tree somewhere the on-call can find it

error downloading terraform version 1.16.3Which content-typeis named?Index stageUpgrade hc-install past PR #57curl -sSIL the same URLfrom the failing hostRelease-side metadataPre-install or pin != 1.16.3A proxy rewrote the headerFix the artifact proxyreleases-api v0 jsonbinary/octet-streambinary/octet-streamapplication/zip

Verify it works

Re-run whatever failed, and check the binary rather than the exit code:

terraform version
# Terraform v1.16.2
# on linux_amd64

For Atlantis, a re-planned pull request is the proof. For acceptance tests, TF_LOG=info go test ... shows which path the harness chose, which is how you catch the TF_ACC_TERRAFORM_PATH trap from step 4.

Then leave the durable check behind, because the real fix lands server-side and nobody will announce it to you. The header test from step 2 is your tripwire:

curl -sSIL https://releases.hashicorp.com/terraform/1.16.3/terraform_1.16.3_linux_amd64.zip \
  | grep -qi 'content-type: application/zip' && echo "re-tagged, drop the pin"

Put that in the same cron or CI job that already checks for stale pins, with a link to #39235 in the comment.

Common pitfalls

  • Clearing caches and re-pulling. Step 3 already proved the bytes are correct. Nothing local is wrong, and every minute spent on Docker layers or ~/.terraform.d is wasted.
  • Blaming tfenv, mise, or the laptop. Those downloaders do not inspect the content type, so a clean local install is perfectly consistent with a hard CI outage. It is not a signal either way.
  • Forking hc-install to skip the MIME check and disabling VerifyChecksum in the same commit. Relaxing the allowlist is defensible precisely because signature and checksum verification run after the download. Turning off the checksum removes the control that made the argument work.
  • Expecting a provider mirror to cover it. filesystem_mirror and network_mirror apply to providers. The CLI binary is fetched by a different mechanism and mirror configuration has no effect on it.
  • A warm GitHub Actions tool cache hiding the break until a cold runner picks up a job, which turns a deterministic failure into an intermittent one. That class of late-surfacing breakage is worth reading up on separately in what breaks after CI goes green.
  • Over-extending the downgrade reasoning. 1.16.3 to 1.16.2 is patch-level and state stays readable. Do not reuse that logic for a minor downgrade after a state format upgrade.

FAQ

Is the Terraform 1.16.3 download corrupt? No. The reporter on hashicorp/terraform#39235 verified the downloaded zip against the published SHA256SUMS and it matched. The rejection happens on the HTTP response header, before any unpacking or verification.

Why does curl or tfenv work while Atlantis fails on the same URL? Only hc-install compares the response content-type against an allowlist. Generic downloaders write the bytes to disk regardless of what the header says.

Will upgrading hc-install fix it? Only if your error names application/vnd+hashicorp.releases-api.v0+json, which is the index-stage check relaxed back in 2022 by PR #57. The binary/octet-stream message comes from a separate check in DownloadAndUnpack() that, as of hc-install#407, has not been changed.

How do I know when to remove the pin? Run the curl -sSIL tripwire from the Verify section. When 1.16.3 answers with application/zip, the object has been re-tagged and the constraint can go.

Wrap-up

You end with a plan that runs, a one-line test that separates the release-side regression from a proxy rewriting headers, and something that tells you when the workaround expires. If you are doing this across more than a handful of pipelines, the lasting fix is to move CLI acquisition behind an internal artifact store with pinned checksums, so a vendor's object metadata never sits on the critical path to a plan again.