{"record":{"id":"7c0ad567040f798d","repo":"hashicorp/terraform","slug":"planoutpath-set-without-also-setting-planoutstates","errorCode":null,"errorMessage":"PlanOutPath set without also setting PlanOutStateStore or PlanOutBackend (this is a bug in Terraform)","messagePattern":"PlanOutPath set without also setting PlanOutStateStore or PlanOutBackend \\(this is a bug in Terraform\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/backend/local/backend_plan.go","lineNumber":163,"sourceCode":"\t\treturn\n\t}\n\n\t// Record whether this plan includes any side-effects that could be applied.\n\trunningOp.PlanEmpty = !plan.Applyable\n\n\t// Save the plan to disk\n\tif path := op.PlanOutPath; path != \"\" {\n\t\tswitch {\n\t\tcase op.PlanOutStateStore != nil:\n\t\t\tplan.StateStore = op.PlanOutStateStore\n\t\tcase op.PlanOutBackend != nil:\n\t\t\tplan.Backend = op.PlanOutBackend\n\t\tdefault:\n\t\t\t// This is always a bug in the operation caller; it's not valid\n\t\t\t// to set PlanOutPath without also setting PlanOutStateStore or PlanOutBackend.\n\t\t\t// Even when there is no state_store or backend block in the configuration, there should be a PlanOutBackend\n\t\t\t// describing the implied local backend.\n\t\t\tdiags = diags.Append(fmt.Errorf(\n\t\t\t\t\"PlanOutPath set without also setting PlanOutStateStore or PlanOutBackend (this is a bug in Terraform)\"),\n\t\t\t)\n\t\t\top.ReportResult(runningOp, diags)\n\t\t\treturn\n\t\t}\n\n\t\t// We may have updated the state in the refresh step above, but we\n\t\t// will freeze that updated state in the plan file for now and\n\t\t// only write it if this plan is subsequently applied.\n\t\tplannedStateFile := statemgr.PlannedStateUpdate(opState, plan.PriorState)\n\n\t\t// We also include a file containing the state as it existed before\n\t\t// we took any action at all, but this one isn't intended to ever\n\t\t// be saved to the backend (an equivalent snapshot should already be\n\t\t// there) and so we just use a stub state file header in this case.\n\t\t// NOTE: This won't be exactly identical to the latest state snapshot\n\t\t// in the backend because it's still been subject to state upgrading\n\t\t// to make it consumable by the current Terraform version, and","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/hashicorp/terraform/blob/d32a084675427f5ac3f7d2868578ef8b2c1dc525/internal/backend/local/backend_plan.go#L145-L181","documentation":"Returned by Local.opPlan when op.PlanOutPath is non-empty but neither op.PlanOutStateStore nor op.PlanOutBackend is set. A plan file must embed a description of where state lives so a later apply can reconcile it; the message explicitly states this is a bug in Terraform because the command layer should always populate one of the two.","triggerScenarios":"Reaching the default branch of the switch at backend_plan.go:153. The CLI/command code that builds the Operation set PlanOutPath but forgot to set PlanOutStateStore (new state-store path) or PlanOutBackend (legacy/implied-local backend). In shipped Terraform this path is unreachable; it appears only with a regression or a custom command.","commonSituations":"Developing a new Terraform command or plan-output path without wiring the backend/store field; a fork that constructs backendrun.Operation manually; running an in-tree test that forgets to populate the backend on the Operation.","solutions":["Report it as a Terraform bug per the message text, including the command that produced it.","If writing command code: always set op.PlanOutBackend (use the implied local backend when there is no backend block) before setting PlanOutPath.","If migrating to the state-store API: set op.PlanOutStateStore instead.","In tests, populate PlanOutBackend with a stub backend to exercise the plan-write path."],"exampleFix":"// before: op set PlanOutPath but no backend/store\nop.PlanOutPath = \"tfplan\"\n// after: also record the (implied) backend so the plan is self-describing\nop.PlanOutPath = \"tfplan\"\nop.PlanOutBackend = impliedBackendForConfig(cfg)","handlingStrategy":"validation","validationCode":"// Command-layer invariant check before Operation is called.\nfunc validatePlanOut(op *backendrun.Operation) error {\n    if op.PlanOutPath != \"\" && op.PlanOutStateStore == nil && op.PlanOutBackend == nil {\n        return fmt.Errorf(\"internal: PlanOutPath set without PlanOutBackend/PlanOutStateStore\")\n    }\n    return nil\n}","typeGuard":"null","tryCatchPattern":"rop, err := b.Operation(ctx, op)\nif err != nil && strings.Contains(err.Error(), \"PlanOutPath set without\") {\n    // this is a Terraform bug; collect op dumps and report\n    return err\n}","preventionTips":["In command code, always set PlanOutBackend (even if it is just the implied local backend) whenever PlanOutPath is set.","Add a test that constructs an Operation with PlanOutPath set and asserts one of PlanOutBackend/PlanOutStateStore is also set.","Treat this message as a development-time assertion, not a user-facing condition."],"tags":["local-backend","plan","internal-bug","invariant"],"backgroundTag":null,"analyzedSha":"d32a084675427f5ac3f7d2868578ef8b2c1dc525","analyzedAt":"2026-08-11T18:43:52.779Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}