{"record":{"id":"33d64d0b26cc0781","repo":"hashicorp/terraform","slug":"unrecognized-action-slice","errorCode":null,"errorMessage":"unrecognized action slice: ","messagePattern":"unrecognized action slice: ","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"internal/command/jsonplan/plan.go","lineNumber":1030,"sourceCode":"\n\tif len(actions) == 1 {\n\t\tswitch actions[0] {\n\t\tcase \"create\":\n\t\t\treturn plans.Create\n\t\tcase \"delete\":\n\t\t\treturn plans.Delete\n\t\tcase \"update\":\n\t\t\treturn plans.Update\n\t\tcase \"read\":\n\t\t\treturn plans.Read\n\t\tcase \"forget\":\n\t\t\treturn plans.Forget\n\t\tcase \"no-op\":\n\t\t\treturn plans.NoOp\n\t\t}\n\t}\n\n\tpanic(\"unrecognized action slice: \" + strings.Join(actions, \", \"))\n}\n\n// encodePaths lossily encodes a cty.PathSet into an array of arrays of step\n// values, such as:\n//\n//\t[[\"length\"],[\"triggers\",0,\"value\"]]\n//\n// The lossiness is that we cannot distinguish between an IndexStep with string\n// key and a GetAttr step. This is fine with JSON output, because JSON's type\n// system means that those two steps are equivalent anyway: both are object\n// indexes.\n//\n// JavaScript (or similar dynamic language) consumers of these values can\n// iterate over the steps starting from the root object to reach the\n// value that each path is describing.\nfunc encodePaths(pathSet cty.PathSet) (json.RawMessage, error) {\n\tif pathSet.Empty() {\n\t\treturn nil, nil","sourceCodeStart":1012,"sourceCodeEnd":1048,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/command/jsonplan/plan.go#L1012-L1048","documentation":"A panic in `UnmarshalActions` (internal/command/jsonplan/plan.go:1030) when a plan's `actions` slice for a resource change does not match any known single action (create/delete/update/read/forget/no-op) or recognized two-action pair (create-then-delete, delete-then-create, create-then-forget). The default case panics, joining the slice into the message. This is an invariant violation: a valid plan JSON only ever contains the enumerated action sets produced by the matching `actionString` encoder.","triggerScenarios":"Reading or applying a JSON plan whose `resource_changes[].change.actions` array contains an unknown action token or an unrecognized 2-element ordering. Typically a version-incompatible plan, a hand-crafted JSON plan, or tooling that emitted a typo'd/garbage action string.","commonSituations":"Forward-generating a plan with a newer Terraform that adds an action type (e.g. a new lifecycle verb) and consuming it with an older binary; corrupted plan artifact; custom automation that synthesizes plan JSON.","solutions":["Regenerate the plan with the same Terraform binary that will apply it.","If you produce/consume plan JSON programmatically, restrict actions to the known set: create, delete, update, read, forget, no-op, and the pairs [create,delete]/[delete,create]/[create,forget].","Treat plan files as opaque artifacts; do not edit action arrays by hand.","Report a bug if reproduced with stock Terraform and an untouched plan."],"exampleFix":"// before: validating a plan JSON consumer by trusting actions\nfunc toPlanAction(actions []string) plans.Action {\n    return jsonplan.UnmarshalActions(actions) // panics on unknown\n}\n\n// after: pre-validate against the known allow-list\nvar validActions = map[string]bool{\"create\": true, \"delete\": true, \"update\": true, \"read\": true, \"forget\": true, \"no-op\": true}\nfunc toPlanAction(actions []string) (plans.Action, error) {\n    for _, a := range actions {\n        if !validActions[a] {\n            return 0, fmt.Errorf(\"unrecognized action %q; regenerate plan\", a)\n        }\n    }\n    return jsonplan.UnmarshalActions(actions)\n}","handlingStrategy":"validation","validationCode":"// Go: pre-validate action slices before calling UnmarshalActions\nvar validSingle = map[string]bool{\"create\": true, \"delete\": true, \"update\": true, \"read\": true, \"forget\": true, \"no-op\": true}\nfunc safeUnmarshal(actions []string) (plans.Action, error) {\n\tfor _, a := range actions {\n\t\tif !validSingle[a] {\n\t\t\treturn 0, fmt.Errorf(\"unrecognized action %q in plan; regenerate with current terraform\", a)\n\t\t}\n\t}\n\treturn jsonplan.UnmarshalActions(actions)\n}","typeGuard":null,"tryCatchPattern":"// Go: recover from the jsonplan panic\ndefer func() {\n\tif r := recover(); r != nil {\n\t\taction = plans.NoOp\n\t\terr = fmt.Errorf(\"failed to decode actions %v: %v\", actions, r)\n\t}\n}()\naction := jsonplan.UnmarshalActions(actions)","preventionTips":["Only consume plans produced by the same Terraform version.","When generating plan JSON programmatically, restrict actions to the known enum/pairs.","Never edit `change.actions` arrays manually.","Pin Terraform versions across plan/apply environments."],"tags":["terraform","plan","json-plan","panic","actions","internal-bug"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}