hashicorp/terraform · error

resource has an unsupported action reason

Error message

resource %s has an unsupported action reason %s

What it means

Thrown by jsonplan.marshalResource when mapping the internal plans.ResourceInstanceChangeReason enum to its JSON string form. The switch lists a fixed set of reasons (replace-because-cannot-update, tainted, delete-because-*, read-because-*). A planned change whose ActionReason is not one of those mapped constants falls through to default. This usually means the plans package grew a new reason constant that the JSON layer was not updated to recognize.

Solutions

  1. Use the same Terraform version to produce and consume the plan.
  2. Upgrade Terraform so the jsonplan switch covers all current ActionReason values.
  3. If extending Terraform, add the missing case mapping in internal/command/jsonplan/plan.go near line 648.
  4. Report the unmapped reason string from the error message upstream.

Example fix

// before
default:
	return r, fmt.Errorf("resource %s has an unsupported action reason %s", r.Address, rc.ActionReason)

// after (map the new constant to its JSON string)
case plans.ResourceInstanceReplaceBecauseGreaterThan:
	r.ActionReason = ResourceInstanceReplaceBecauseGreaterThan
Defensive patterns

Strategy: validation

Validate before calling

// Guard before marshaling: skip or map only known action reasons.
var knownReasons = map[plans.ResourceInstanceChangeReason]bool{
    plans.ResourceInstanceChangeNoReason: true,
    plans.ResourceInstanceReplaceBecauseCannotUpdate: true,
    plans.ResourceInstanceReplaceBecauseTainted: true,
    plans.ResourceInstanceReplaceByRequest: true,
    plans.ResourceInstanceDeleteBecauseNoResource: true,
    plans.ResourceInstanceDeleteBecauseWrongRepetition: true,
    plans.ResourceInstanceDeleteBecauseCountIndex: true,
    plans.ResourceInstanceDeleteBecauseEachKey: true,
    plans.ResourceInstanceDeleteBecauseNoModule: true,
    plans.ResourceInstanceDeleteBecauseNoMoveTarget: true,
    plans.ResourceInstanceReadBecauseConfigUnknown: true,
    plans.ResourceInstanceReadBecauseDependencyPending: true,
    plans.ResourceInstanceReadBecauseCheckNested: true,
}

if !knownReasons[rc.ActionReason] {
    return fmt.Errorf("skipping change with unmapped action reason %d", rc.ActionReason)
}

Prevention

When it happens

Trigger: A new plans.ResourceInstanceChange*Reason constant is added without a matching case; a provider/SDK path sets an ActionReason value outside the documented set; an internal regression assigns a zero/garbage reason.

Common situations: Cross-binary plan consumption (plan produced by a newer Terraform, shown by an older one); running an unreleased/dev build whose plans and jsonplan packages are out of sync.

Related errors


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

Appendix: source

Thrown at internal/command/jsonplan/plan.go:648

		r.ActionReason = ResourceInstanceDeleteBecauseNoResourceConfig
	case plans.ResourceInstanceDeleteBecauseWrongRepetition:
		r.ActionReason = ResourceInstanceDeleteBecauseWrongRepetition
	case plans.ResourceInstanceDeleteBecauseCountIndex:
		r.ActionReason = ResourceInstanceDeleteBecauseCountIndex
	case plans.ResourceInstanceDeleteBecauseEachKey:
		r.ActionReason = ResourceInstanceDeleteBecauseEachKey
	case plans.ResourceInstanceDeleteBecauseNoModule:
		r.ActionReason = ResourceInstanceDeleteBecauseNoModule
	case plans.ResourceInstanceDeleteBecauseNoMoveTarget:
		r.ActionReason = ResourceInstanceDeleteBecauseNoMoveTarget
	case plans.ResourceInstanceReadBecauseConfigUnknown:
		r.ActionReason = ResourceInstanceReadBecauseConfigUnknown
	case plans.ResourceInstanceReadBecauseDependencyPending:
		r.ActionReason = ResourceInstanceReadBecauseDependencyPending
	case plans.ResourceInstanceReadBecauseCheckNested:
		r.ActionReason = ResourceInstanceReadBecauseCheckNested
	default:
		return r, fmt.Errorf("resource %s has an unsupported action reason %s", r.Address, rc.ActionReason)
	}

	return r, nil
}

// MarshalDeferredResourceChanges converts the provided internal representation
// of DeferredResourceInstanceChangeSrc objects into the public structured JSON
// changes.
// This is public to make testing easier.
func MarshalDeferredResourceChanges(resources []*plans.DeferredResourceInstanceChangeSrc, schemas *terraform.Schemas) ([]DeferredResourceChange, error) {
	var ret []DeferredResourceChange

	var sortedResources []*plans.DeferredResourceInstanceChangeSrc
	sortedResources = append(sortedResources, resources...)
	sort.Slice(sortedResources, func(i, j int) bool {
		if !sortedResources[i].ChangeSrc.Addr.Equal(sortedResources[j].ChangeSrc.Addr) {
			return sortedResources[i].ChangeSrc.Addr.Less(sortedResources[j].ChangeSrc.Addr)
		}

View on GitHub (pinned to d32a084675)