hashicorp/terraform · error

Failed to retrieve token

Error message

Failed to retrieve token: %s

What it means

Thrown by the manual-token branch of login.go when c.UIInput().Input fails for the token prompt. This is the path where the user pastes a token (as opposed to the OAuth/password flows); the error means the prompt to read the token could not complete, not that the token is invalid. Token validity is checked afterwards via tfe.NewClient.

Solutions

  1. Set the token directly via `TF_TOKEN_<hostname>` env var or write it to ~/.terraform.d/credentials.tfrc.json.
  2. Run `terraform login` in a real terminal so the prompt can be read.
  3. Use the browser OAuth flow instead of manual token entry.
  4. Ensure stdin is an interactive TTY when manual entry is required.

Example fix

# before: terraform login (token path) with no TTY -> 'Failed to retrieve token'

# after: write the credentials file directly
cat > ~/.terraform.d/credentials.tfrc.json <<EOF
{ "credentials": { "app.terraform.io": { "token": "<token>" } } }
EOF
terraform init
Defensive patterns

Strategy: validation

Validate before calling

if !canPrompt() { // see error 637's canPrompt()
    return errors.New("no TTY: set TF_TOKEN_<hostname> or write ~/.terraform.d/credentials.tfrc.json")
}

Try / catch

token, err := c.UIInput().Input(ctx, opts)
if err != nil {
    if !canPrompt() {
        return fmt.Errorf("token prompt needs a TTY; set TF_TOKEN_%s or use the OAuth flow: %w", hostname, err)
    }
    return err
}

Prevention

When it happens

Trigger: Non-interactive environment with no TTY; the token prompt was cancelled, empty, or EOF'd; a custom UIInput backend errored on the secret prompt.

Common situations: Running `terraform login` in CI/containers; stdin closed or piped; user aborted at the token prompt; automating login where a TTY is unavailable.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/9c2112e0334f54b0. Report an issue: GitHub.

Appendix: source

Thrown at internal/command/login.go:644

	// credsCtx might not be set if we're using a mock credentials source
	// in a test, but it should always be set in normal use.
	if credsCtx != nil {
		switch credsCtx.Location {
		case cliconfig.CredentialsViaHelper:
			c.Ui.Output(fmt.Sprintf("Terraform will store the token in the configured %q credentials helper\nfor use by subsequent commands.\n", credsCtx.HelperType))
		case cliconfig.CredentialsInPrimaryFile, cliconfig.CredentialsNotAvailable:
			c.Ui.Output(fmt.Sprintf("Terraform will store the token in plain text in the following file\nfor use by subsequent commands:\n    %s\n", credsCtx.LocalFilename))
		}
	}

	token, err := c.UIInput().Input(context.Background(), &terraform.InputOpts{
		Id:     "token",
		Query:  fmt.Sprintf("Token for %s:", hostname.ForDisplay()),
		Secret: true,
	})
	if err != nil {
		diags := diags.Append(fmt.Errorf("Failed to retrieve token: %s", err))
		return "", diags
	}

	token = strings.TrimSpace(token)
	cfg := &tfe.Config{
		Address:  service.String(),
		BasePath: service.Path,
		Token:    token,
		Headers:  make(http.Header),
	}
	client, err := tfe.NewClient(cfg)
	if err != nil {
		diags = diags.Append(fmt.Errorf("Failed to create API client: %s", err))
		return "", diags
	}
	user, err := client.Users.ReadCurrent(context.Background())
	if err == tfe.ErrUnauthorized {
		diags = diags.Append(fmt.Errorf("Token is invalid: %s", err))

View on GitHub (pinned to d32a084675)