hashicorp/terraform · warning

Login cancelled

Error message

Login cancelled

What it means

Returned by `terraform login` when the user answers "no" to the OAuth authorization-code consent prompt shown by `interactiveContextConsent` before the code-grant flow begins. It is a deliberate user abort, not a library failure: the command appends it as a diagnostic and returns no token.

Solutions

  1. Re-run `terraform login <hostname>` and answer "yes" at the consent prompt to proceed with the OAuth code grant.
  2. If running non-interactively, set the credentials directly in ~/.terraform.d/credentials.tfrc.json or supply a TF_TOKEN_<hostname> environment variable instead of using the interactive flow.
  3. Verify the hostname argument matches a host that actually serves `terraform-login` disco metadata.

Example fix

// before: terraform login app.terraform.io  -> user types 'no'
// after:  terraform login app.terraform.io  -> user types 'yes' at consent
Defensive patterns

Strategy: validation

Validate before calling

// Before invoking the login flow, gate on input capability:
if !cmd.Input() {
    return errors.New("cannot perform interactive OAuth login with input disabled; set TF_TOKEN_<host> instead")
}

Prevention

When it happens

Trigger: `LoginCommand.interactiveGetTokenByCode` runs for a host whose disco metadata advertises an OAuth authorization-code grant; `interactiveContextConsent` returns `confirm=false` because the user typed a negative answer at the consent prompt.

Common situations: Running `terraform login app.terraform.io` (or a private TFE hostname) in a terminal and selecting "no" / typing something other than yes when asked to approve the authorization request; CI shells where stdin returns an empty or non-affirmative answer.

Related errors


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

Appendix: source

Thrown at internal/command/login.go:373

// Synopsis implements cli.Command.
func (c *LoginCommand) Synopsis() string {
	return "Obtain and save credentials for a remote host"
}

func (c *LoginCommand) defaultOutputFile() string {
	if c.CLIConfigDir == "" {
		return "" // no default available
	}
	return filepath.Join(c.CLIConfigDir, "credentials.tfrc.json")
}

func (c *LoginCommand) interactiveGetTokenByCode(hostname svchost.Hostname, credsCtx *loginCredentialsContext, clientConfig *disco.OAuthClient) (*oauth2.Token, tfdiags.Diagnostics) {
	var diags tfdiags.Diagnostics

	confirm, confirmDiags := c.interactiveContextConsent(hostname, disco.OAuthAuthzCodeGrant, credsCtx)
	diags = diags.Append(confirmDiags)
	if !confirm {
		diags = diags.Append(errors.New("Login cancelled"))
		return nil, diags
	}

	// We'll use an entirely pseudo-random UUID for our temporary request
	// state. The OAuth server must echo this back to us in the callback
	// request to make it difficult for some other running process to
	// interfere by sending its own request to our temporary server.
	reqState, err := uuid.GenerateUUID()
	if err != nil {
		// This should be very unlikely, but could potentially occur if e.g.
		// there's not enough pseudo-random entropy available.
		diags = diags.Append(tfdiags.Sourceless(
			tfdiags.Error,
			"Can't generate login request state",
			fmt.Sprintf("Cannot generate random request identifier for login request: %s.", err),
		))
		return nil, diags
	}

View on GitHub (pinned to d32a084675)