hashicorp/terraform · error

Unknown or unexpected cost estimate state

Error message

Unknown or unexpected cost estimate state: %s

What it means

Cost-estimate state switch hit its default: ce.Status is a value not present in the enumerated tfe.CostEstimate* constants known to this binary. Almost always a client/server version skew—the TFE server returned a newer status string the client doesn't recognize.

Solutions

  1. Upgrade the Terraform/OpenTofu binary to a version whose bundled tfe SDK knows the new status.
  2. Downgrade the TFE server side temporarily if upgrade is not possible.
  3. Report the unknown status value (visible in %s) to confirm it is a real enum and not corruption.
Defensive patterns

Strategy: validation

Validate before calling

func knownCostEstimateStatus(s tfe.CostEstimateStatus) bool {
    switch s {
    case tfe.CostEstimatePending, tfe.CostEstimateQueued, tfe.CostEstimateStarting,
         tfe.CostEstimateRunning, tfe.CostEstimateFinished, tfe.CostEstimateErrored,
         tfe.CostEstimateCanceled, tfe.CostEstimateSkippedDueToTargeting:
        return true
    }
    return false
}

Type guard

func isKnownCostEstimateStatus(s tfe.CostEstimateStatus) bool {
    return s >= tfe.CostEstimatePending && s <= tfe.CostEstimateSkippedDueToTargeting
}

Prevention

When it happens

Trigger: ce.Status is set by the server to a value outside {Pending, Queued, Starting, Running, Finished, Errored, Canceled, SkippedDueToTargeting, ...}. Happens when a newer TFE server introduces a new status and the client predates it.

Common situations: OpenTofu/Terraform binary built against an older tfe SDK hitting a freshly upgraded TFE; preview features adding intermediate states; rarely a corrupted/empty status field.

Related errors


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

Appendix: source

Thrown at internal/cloud/backend_common.go:318

						" (%s elapsed)", current.Sub(started).Truncate(30*time.Second))
				}
				b.CLI.Output(b.Colorize().Color("[bold]" + msgPrefix + ":\n"))
				b.CLI.Output(b.Colorize().Color("Waiting for cost estimate to complete..." + elapsed + "\n"))
			}
			continue
		case tfe.CostEstimateSkippedDueToTargeting:
			b.CLI.Output(b.Colorize().Color("[bold]" + msgPrefix + ":\n"))
			b.CLI.Output("Not available for this plan, because it was created with the -target option.")
			b.CLI.Output("\n------------------------------------------------------------------------")
			return nil
		case tfe.CostEstimateErrored:
			b.CLI.Output(msgPrefix + " errored.\n")
			b.CLI.Output("\n------------------------------------------------------------------------")
			return nil
		case tfe.CostEstimateCanceled:
			return fmt.Errorf("%s canceled.", msgPrefix)
		default:
			return fmt.Errorf("Unknown or unexpected cost estimate state: %s", ce.Status)
		}
	}
}

func (b *Cloud) checkPolicy(stopCtx, cancelCtx context.Context, op *backendrun.Operation, r *tfe.Run) error {
	if b.CLI != nil {
		b.CLI.Output("\n------------------------------------------------------------------------\n")
	}
	for i, pc := range r.PolicyChecks {
		// Read the policy check logs. This is a blocking call that will only
		// return once the policy check is complete.
		logs, err := b.client.PolicyChecks.Logs(stopCtx, pc.ID)
		if err != nil {
			return b.generalError("Failed to retrieve policy check logs", err)
		}
		reader := bufio.NewReaderSize(logs, 64*1024)

		// Retrieve the policy check to get its current status.

View on GitHub (pinned to d32a084675)