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
- Inspect the wrapped error for an HTTP status—refresh the token on 401, back off on 429.
- Verify network/TLS connectivity to the configured hostname from the runner.
- Retry transient 5xx with exponential backoff at the workflow level.
- 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
- Refresh long-lived tokens or use short-lived dynamic credentials.
- Verify TLS/DNS to the TFE hostname from the runner.
- Retry 429/5xx with exponential backoff.
- Keep the tfe client SDK close to the server version.
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
- error creating workspace
- error finding remote workspace
- error loading workspace
- error loading workspace
- workspace not found For security, returns '404 Not Found'…
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)