hashicorp/terraform · error

returned an unexpected error

Error message

%s returned an unexpected error:

%s

What it means

fetchWorkspace's default branch: Workspaces.Read returned an error that is neither context.Canceled nor tfe.ErrResourceNotFound. The backend wraps and rethrows so callers see which host produced what. This is the catch-all for transport, auth, rate-limit, and server errors.

Solutions

  1. Inspect the wrapped error for an HTTP status—refresh the token on 401, back off on 429.
  2. Verify network/TLS connectivity to the configured hostname from the runner.
  3. Retry transient 5xx with exponential backoff at the workflow level.
  4. Confirm the TFE client version bundled with the binary matches the server's API surface.
Defensive patterns

Strategy: retry

Validate before calling

func reachable(client *tfe.Client) error {
    // ping the org to detect auth/transport issues early
    if _, err := client.Organizations.Read(ctx, org); err != nil {
        return fmt.Errorf("TFE unreachable or unauthorized: %w", err)
    }
    return nil
}

Type guard

func isRetryableTFE(err error) bool {
    var apiErr *tfeerror.Error
    if errors.As(err, &apiErr) {
        switch apiErr.Status {
        case 429, 500, 502, 503, 504:
            return true
        }
    }
    return errors.Is(err, context.DeadlineExceeded) || isTransientNet(err)
}

Try / catch

backoff.Retry(func() error {
    ws, err := client.Workspaces.Read(ctx, org, name)
    if isRetryableTFE(err) { return err }
    if err != nil { return backoff.Permanent(err) }
    _ = ws
    return nil
}, backoff.NewExponentialBackOff())

Prevention

When it happens

Trigger: Workspaces.Read returns non-canceled, non-404: 401 unauthorized (bad/expired token); 429 rate limit; 500/502/503 from TFE; DNS/TLS failure to the configured hostname; malformed response deserialization in the TFE client.

Common situations: Expired API token; TFE instance behind a flaky load balancer; hitting rate limits during a large matrix CI run; self-hosted TFE cert renewed with a chain the runner does not trust.

Related errors


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

Appendix: source

Thrown at internal/cloud/backend.go:1344

}

func (b *Cloud) fetchWorkspace(ctx context.Context, organization string, workspace string) (*tfe.Workspace, error) {
	// Retrieve the workspace for this operation.
	w, err := b.client.Workspaces.Read(ctx, organization, workspace)
	if err != nil {
		switch err {
		case context.Canceled:
			return nil, err
		case tfe.ErrResourceNotFound:
			return nil, fmt.Errorf(
				"workspace %s not found\n\n"+
					fmt.Sprintf("For security, %s returns '404 Not Found' responses for resources\n", b.appName)+
					"for resources that a user doesn't have access to, in addition to resources that\n"+
					"do not exist. If the resource does exist, please check the permissions of the provided token.",
				workspace,
			)
		default:
			err := fmt.Errorf(
				"%s returned an unexpected error:\n\n%s",
				b.appName,
				err,
			)
			return nil, err
		}
	}

	return w, nil
}

// validWorkspaceEnvVar ensures we have selected a valid workspace using TF_WORKSPACE:
// First, it ensures the workspace specified by TF_WORKSPACE exists in the organization.
// (This is because we deliberately DON'T implicitly create a workspace from TF_WORKSPACE,
// unlike with a workspace specified via `name`.)
// Second, if tags are specified in the configuration, it ensures TF_WORKSPACE belongs to the set
// of available workspaces with those given tags.
func (b *Cloud) validWorkspaceEnvVar(ctx context.Context, organization, workspace string) tfdiags.Diagnostic {

View on GitHub (pinned to d32a084675)