hashicorp/terraform · error

ErrInvalidRemotePlanFormat

ErrInvalidRemotePlanFormat

Error message

invalid remote plan format, must be 1

What it means

Exported sentinel ErrInvalidRemotePlanFormat (cloudplan/saved_plan.go:13, code=ErrInvalidRemotePlanFormat). When reading a saved-plan bookmark file, StateRead/Read unmarshals JSON and checks bookmark.RemotePlanFormat == 1; any other value (0 from an empty/garbage file, or a future incompatible format) returns this error. As the comments at saved_plan.go:50-54 note, these errors imply the file probably is not a saved plan bookmark at all.

Source

Thrown at internal/cloud/cloudplan/saved_plan.go:13

// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package cloudplan

import (
	"encoding/json"
	"errors"
	"io"
	"os"
	"strings"
)

var ErrInvalidRemotePlanFormat = errors.New("invalid remote plan format, must be 1")
var ErrInvalidRunID = errors.New("invalid run ID")
var ErrInvalidHostname = errors.New("invalid hostname")

type SavedPlanBookmark struct {
	RemotePlanFormat int    `json:"remote_plan_format"`
	RunID            string `json:"run_id"`
	Hostname         string `json:"hostname"`
}

func NewSavedPlanBookmark(runID, hostname string) SavedPlanBookmark {
	return SavedPlanBookmark{
		RemotePlanFormat: 1,
		RunID:            runID,
		Hostname:         hostname,
	}
}

func LoadSavedPlanBookmark(filepath string) (SavedPlanBookmark, error) {

View on GitHub (pinned to d32a084675)

Solutions

  1. Re-generate the plan file with `terraform plan -out=<file>` using the same backend, then pass it to terraform show/apply.
  2. Confirm you are using the cloud/remote backend for both the plan and the show/apply step.
  3. If the file is hand-edited or from another tool, treat it as not-a-plan and regenerate it.
Defensive patterns

Strategy: validation

Validate before calling

// Peek at the bookmark before handing it to StateRead.
func isValidRemoteBookmark(b cloudplan.SavedPlanBookmark) error {
    if b.RemotePlanFormat != 1 { return cloudplan.ErrInvalidRemotePlanFormat }
    if b.Hostname == "" { return cloudplan.ErrInvalidHostname }
    if b.RunID == "" || !strings.HasPrefix(b.RunID, "run-") { return cloudplan.ErrInvalidRunID }
    return nil
}

Type guard

func isRemotePlanBookmark(data []byte) bool {
    var b cloudplan.SavedPlanBookmark
    if err := json.Unmarshal(data, &b); err != nil { return false }
    return b.RemotePlanFormat == 1
}

Try / catch

b, err := cloudplan.StateRead(path)
if err != nil {
    if errors.Is(err, cloudplan.ErrInvalidRemotePlanFormat) {
        // not a cloud plan file; try local plan path or re-plan
    }
}

Prevention

When it happens

Trigger: terraform show/apply pointing at a plan file (e.g. `terraform show foo.tfplan`) where foo.tfplan is not a cloud saved-plan bookmark — e.g. a local plan, a corrupt file, or unrelated bytes — so RemotePlanFormat != 1 after json.Unmarshal.

Common situations: Mixing up a local-backend plan file with a cloud-backend bookmark. Passing a stale plan from a different Terraform version. A truncated/empty file producing a zero-value struct.

Related errors


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