Private module registry modules.v1 /versions returns an empty collection, breaking terraform init
Category: Terraform (Terraform - HashiCorp Discuss)
GET /api/registry/v1/modules/{ns}/{name}/{provider}/versions on app.terraform.io returns 200 with an empty list instead of the module’s versions when authenticated with a team token for a non-owners team. With an owners-team token, the identical request returns the full version list.
| Request | Non-owners team token | Owners team token |
|---|---|---|
v1/modules/myorg/mymodule/azurerm/versions |
200 {"modules":[]} |
200, full version list |
The docs say a team token is enough for this — Use artifacts from the private registry states “All users in an organization can view the private registry and use the available providers, Stack component configurations, and modules”, and “Team Token: Allows you to access the private registry of that team’s organization and the registries from any other organizations that have configured sharing.”
The only owners-scoped registry row in the API tokens access-levels chart is “Manage Terraform modules”, which is publishing, not consuming.
More pointedly, the HashiCorp support guide How to create a Terraform API token with read-only access to the private module registry prescribes exactly the setup that fails for me — create a team, “ensure that no permissions are selected”, generate a team token — and states the outcome is “a team API token that can be used to authenticate with and read from the Private Module Registry, suitable for use in CI/CD pipelines”. My team has no organization permissions, which is what that guide asks for.
And with the non-owners token the failure is silent and isolated to that one endpoint: the namespace listing returns every module in the namespace with its versions, the module endpoint returns 0.0.6, and the download endpoint returns a working archivist URL for that same 0.0.6. The credential can fetch the artifact but cannot be told it exists. If it really lacks read access, this should be a 403 — an empty 200 leaves Terraform reporting “invalid response” instead of an auth error.
Every terraform init with the team token fails.
Environment
- HCP Terraform (
app.terraform.io), Free tier - Terraform CLI 1.12.2, reproduced on
darwin_arm64locally andlinux_amd64in CI - Auth: team API token.
GET /api/v2/account/detailsreturns200withis-service-account: true - Reproduced on two different modules in the same namespace
Reproduction
Identical Authorization: Bearer <team token> on every request, captured 2026-08-24.
| # | Request (prefix /api/registry/) |
Actual |
|---|---|---|
| 1 | v1/modules/myorg/mymodule/azurerm/versions |
200 {"modules":[]} |
| 2 | v1/modules/myorg/mymodule/azurerm |
200, id myorg/mymodule/azurerm/0.0.6 |
| 3 | v1/modules/myorg |
200, every module in the namespace listed with versions |
| 4 | v1/modules/myorg/mymodule/azurerm/0.0.6/download |
204 + x-terraform-get: https://archivist.terraform.io/... |
| 5 | private/v1/modules/myorg/mymodule/azurerm/versions |
200, full version list |
| 6 | private/v1/modules/myorg/mymodule/azurerm/0.0.6/download |
404 |
| 7 | v1/providers/myorg/myprovider/versions |
200, full version list |
| 8 | v1/modules/myorg/othermodule/azurerm/versions |
200 {"modules":[]} |
TF_LOG=trace output
Minimal reproduction, a directory containing only:
module "kv" {
source = "app.terraform.io/myorg/mymodule/azurerm"
version = "0.0.6"
}
TF_TOKEN_app_terraform_io=<team token> TF_LOG=trace terraform init, exit 1:
[TRACE] ModuleInstaller: kv is a registry module at app.terraform.io/myorg/mymodule/azurerm
[DEBUG] kv listing available versions of app.terraform.io/myorg/mymodule/azurerm at app.terraform.io
[DEBUG] Service discovery for app.terraform.io at https://app.terraform.io/.well-known/terraform.json
[DEBUG] fetching module versions from "https://app.terraform.io/api/registry/v1/modules/myorg/mymodule/azurerm/versions"
[DEBUG] GET https://app.terraform.io/api/registry/v1/modules/myorg/mymodule/azurerm/versions
Error: Invalid response from remote module registry
Service discovery resolves modules.v1, exactly one request goes out — the endpoint in row 1 — and init fails on the empty collection.
With a CLI host block overriding modules.v1 to .../api/registry/private/v1/modules/, init gets one step further and dies on
the download instead:
Downloading app.terraform.io/myorg/mymodule/azurerm 0.0.6 for kv...
[DEBUG] GET https://app.terraform.io/api/registry/private/v1/modules/myorg/mymodule/azurerm/0.0.6/download
[ERROR] kv ... module "app.terraform.io/myorg/mymodule/azurerm" version "0.0.6" not found
Error: Error accessing remote module registry
Failed to retrieve a download URL for ... version "0.0.6" not found
Same version the unprefixed base downloads fine (row 4).
Why I read this as a server-side bug
- Request 1 is the only endpoint
terraform initcan use — service discovery at/.well-known/terraform.jsonreturns"modules.v1":"/api/registry/v1/modules/", the same authenticated or not. - Whatever access rule produces the empty list isn’t applied consistently: with the same non-owners token, request 4 returns a live signed download URL for
0.0.6and request 3 lists the namespace with version numbers attached. Requests 2 and 7 also succeed./versionsis the only endpoint in the chain that behaves as though the token were unauthorized — and it does so silently. - Not module-specific (request 8).
- No client-side workaround.
versionsworks only under theprivatebase anddownloadonly under the unprefixed base, and Terraform uses one base URL for both. With a CLIhostblock pointingmodules.v1at/api/registry/private/v1/modules/, version resolution succeeds (Downloading app.terraform.io/myorg/mymodule/azurerm 0.0.6) then fails withFailed to retrieve a download URL ... not found.
Not a regression
To head off the obvious question: nothing broke. Earlier successful inits against this registry used a different credential type. A team token only became available once SSO was enabled on the org, which created a built-in team I could attach a token to — before that there was no suitable team. The first team token I used behaved this way, and it has never worked.
Questions
- Is requiring an owners-team token to resolve private module versions intended? If so,
registry/usingneeds correcting, because it says any team token grants private registry access and scopes only “Manage Terraform modules” to owners. - Either way, should this be a
403rather than an empty200? As it stands the CLI cannot distinguish “you may not see this” from “this module has no versions”, and reports “invalid response” for what is really an authorization outcome. - If owners really is required, how are people running least-privilege CI against the private registry? Making every pipeline token an org owner is not a posture I want to adopt.
- Has anyone seen
/versionsreturn empty while the sibling endpoints on the same base path — includingdownloadfor the very version it omits — work?
Current workaround is pinning module sources to git refs, which bypasses the registry and loses version constraints.