hashicorp/terraform · error

operation timed out

Error message

operation timed out

What it means

Returned by the remote backend plan path (backend_plan.go:292) when the uploaded configuration version never reaches tfe.ConfigurationUploaded status within the polling loop. The backend uploads the config then polls ConfigurationVersions.Read; if uploaded is still false after all iterations, it wraps a plain 'operation timed out' error in generalError with the message 'Failed to upload configuration files'. The cause is usually TFE never acknowledging the upload (network, large config, slow/overloaded server), not an explicit wall-clock timeout.

Solutions

  1. Retry terraform plan — transient server/network issues are the most common cause.
  2. Check the TFE/HCP workspace UI for the configuration version status and any ingest errors.
  3. Reduce config size or use .terraformignore to skip large vendored files, then retry.
  4. Verify connectivity and proxies between your runner and the TFE hostname.
Defensive patterns

Strategy: retry

Try / catch

cv, err := b.uploadConfig(...)
if err != nil {
    if strings.Contains(err.Error(), "operation timed out") {
        // TFE did not acknowledge the upload in time; retry with backoff
        return retryUpload()
    }
    return err
}

Prevention

When it happens

Trigger: Running terraform plan with the 'remote' backend against a TFE/HCP server where the configuration-version upload completes the HTTP request but the server fails to flip the CV to ConfigurationUploaded within the poll window (the upload-then-poll loop in backend_plan.go).

Common situations: Very large config bundles that take longer than the poll budget to ingest. TFE sidecar/worker backpressure or outages. Network proxies that complete the upload but drop the status-read. A stale upload URL or an interrupted connection between upload and status read.

Understand the failure class

Related errors


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

Appendix: source

Thrown at internal/backend/remote/backend_plan.go:292

		case <-cancelCtx.Done():
			log.Printf("[TRACE] backend/remote: operation cancelled while waiting for configuration status")
			return nil, context.Canceled
		case <-time.After(planConfigurationVersionsPollInterval):
			log.Printf("[TRACE] backend/remote: reading configuration status")
			cv, err = b.client.ConfigurationVersions.Read(stopCtx, cv.ID)
			if err != nil {
				return nil, generalError("Failed to retrieve configuration version", err)
			}

			if cv.Status == tfe.ConfigurationUploaded {
				uploaded = true
			}
		}
	}

	if !uploaded {
		return nil, generalError(
			"Failed to upload configuration files", errors.New("operation timed out"))
	}

	log.Printf("[TRACE] backend/remote: configuration uploaded and ready")
	runOptions := tfe.RunCreateOptions{
		ConfigurationVersion: cv,
		Refresh:              tfe.Bool(op.PlanRefresh),
		Workspace:            w,
	}

	switch op.PlanMode {
	case plans.NormalMode:
		// okay, but we don't need to do anything special for this
	case plans.RefreshOnlyMode:
		runOptions.RefreshOnly = tfe.Bool(true)
	case plans.DestroyMode:
		runOptions.IsDestroy = tfe.Bool(true)
	default:
		// Shouldn't get here because we should update this for each new

View on GitHub (pinned to d32a084675)