opentofu/opentofu · error

can't show a saved cloud plan unless the current root module

Error message

can't show a saved cloud plan unless the current root module is connected to Terraform Cloud

What it means

getDataFromCloudPlan runs when the file passed to tofu show is a saved cloud plan bookmark (cloudplan.SavedPlanBookmark, containing a run ID and hostname) rather than a local plan. It requires the backend resolved from the current configuration to be *cloud.Cloud, i.e. the root module must be connected to Terraform Cloud via the cloud block. If the current configuration resolves to any other backend, the plan JSON cannot be fetched and this error is returned.

Source

Thrown at internal/command/show.go:402

		plan, stateFile, config, err = getDataFromPlanfileReader(ctx, lp, rootCall)
	} else if cp, ok := pf.Cloud(); ok {
		redacted := c.viewType != arguments.ViewJSON
		jsonPlan, err = c.getDataFromCloudPlan(ctx, cp, redacted, enc)
	}

	return plan, jsonPlan, stateFile, config, err
}

func (c *ShowCommand) getDataFromCloudPlan(ctx context.Context, plan *cloudplan.SavedPlanBookmark, redacted bool, enc encryption.Encryption) (*cloudplan.RemotePlanJSON, error) {
	// Set up the backend
	b, backendDiags := c.Backend(ctx, nil, enc.State())
	if backendDiags.HasErrors() {
		return nil, errUnusable(backendDiags.Err(), "cloud plan")
	}
	// Cloud plans only work if we're cloud.
	cl, ok := b.(*cloud.Cloud)
	if !ok {
		return nil, errUnusable(fmt.Errorf("can't show a saved cloud plan unless the current root module is connected to Terraform Cloud"), "cloud plan")
	}

	result, err := cl.ShowPlanForRun(context.Background(), plan.RunID, plan.Hostname, redacted)
	if err != nil {
		err = errUnusable(err, "cloud plan")
	}
	return result, err
}

// maybeGetSchemas is a thin wrapper around [Meta.MaybeGetSchemas] that
// takes a [*statefile.File] instead of a [*states.State] and tolerates
// the state file being nil, since that's more convenient for the
// "tofu show" methods that may or may not have a state file to use.
func (c *ShowCommand) maybeGetSchemas(ctx context.Context, stateFile *statefile.File, config *configs.Config) (*tofu.Schemas, tfdiags.Diagnostics) {
	ctx, span := tracing.Tracer().Start(ctx, "Get Schemas")
	defer span.End()

	if stateFile == nil {

View on GitHub (pinned to 3561785c48)

Solutions

  1. Restore the cloud block configuration that produced the plan and run tofu init, then retry tofu show <plan>
  2. View the plan in the Terraform Cloud UI for the run ID embedded in the bookmark instead of locally
  3. Regenerate the plan locally with the current backend if cloud access is not intended
  4. Confirm you are in the same root module directory that created the plan file

Example fix

# before
# plan saved under cloud backend, cloud block now commented out
tofu show tfplan   # can't show a saved cloud plan

# after
terraform {
  cloud {
    organization = "my-org"
    workspaces { name = "ws" }
  }
}
# then: tofu init && tofu show tfplan
Defensive patterns

Strategy: validation

Validate before calling

raw, err := os.ReadFile(planPath)
if err != nil {
	return err
}
if bytes.Contains(raw, []byte("run_id")) {
	// saved cloud plan bookmark: tofu show needs the cloud{} backend active,
	// otherwise it fails with the 'connected to Terraform Cloud' error
	if !cloudBackendActive() {
		return errors.New("plan is a cloud bookmark; restore the cloud block or view it in TFC")
	}
}

Type guard

func isCloudPlanBookmark(raw []byte) bool {
	var probe struct {
		CloudPlan *struct {
			RunID    string `json:"run_id"`
			Hostname string `json:"hostname"`
		} `json:"cloud_plan"`
	}
	return json.Unmarshal(raw, &probe) == nil && probe.CloudPlan != nil
}

Try / catch

result, err := getDataFromCloudPlan(ctx, bookmark, redacted, enc)
if err != nil {
	if strings.Contains(err.Error(), "connected to Terraform Cloud") {
		// backend mismatch: point the user at the TFC run instead of retrying
		return fmt.Errorf("open the plan at https://%s/app/.../runs/%s", bookmark.Hostname, bookmark.RunID)
	}
	return err
}

Prevention

When it happens

Trigger: Running tofu show <plan-file> where the file was written by a run driven by the cloud backend, while the current root module has no cloud block or uses a backend block; commenting out the cloud block between saving and showing the plan; running show from a different directory whose backend differs.

Common situations: Viewing a cloud-run plan artifact on a laptop checkout that uses a local or S3 backend; mid-migration off Terraform Cloud; plan files shared between machines or branches with different backend configs.

Related errors


AI-assisted analysis of opentofu/opentofu@3561785c48 (2026-08-15). Data as JSON: /api/errors/5d12331ead96456a. Report an issue: GitHub.