hashicorp/terraform · error
Failed to marshal index step key %#v
Error message
Failed to marshal index step key %#v: %s
What it means
Thrown by jsonplan.encodePath when converting a cty.Path (used for sensitive-attribute paths and diff paths) to JSON. For a cty.IndexStep, the key is encoded with ctyjson.Marshal(s.Key, s.Key.Type()). If that marshal fails (e.g. the key is a null/unknown value, or has an exotic cty type ctyjson cannot encode), the error wraps the underlying failure. The plan JSON then cannot represent which indexed element is sensitive/changed.
Solutions
- Upgrade Terraform/ctyjson; newer versions handle more key types.
- Simplify the index key type (use strings/numbers rather than complex objects as for_each keys).
- Reduce sensitivity markers on dynamically-indexed attributes to isolate the offending path.
- Reproduce with `terraform plan -json` and report the key type (%#v) shown in the message upstream.
Example fix
// before
key, err := ctyjson.Marshal(s.Key, s.Key.Type())
if err != nil {
return nil, fmt.Errorf("Failed to marshal index step key %#v: %s", s.Key, err)
}
// after (handle null/unknown keys defensively before encoding)
if !s.Key.IsKnown() || s.Key.IsNull() {
return nil, fmt.Errorf("cannot encode path with unknown/null index key: %#v", s.Key)
}
key, err := ctyjson.Marshal(s.Key, s.Key.Type()) Defensive patterns
Strategy: try-catch
Validate before calling
// Reject null/unknown index keys before they reach ctyjson.
func encodablePath(p cty.Path) error {
for _, step := range p {
if idx, ok := step.(cty.IndexStep); ok {
if !idx.Key.IsKnown() || idx.Key.IsNull() {
return fmt.Errorf("path contains unencodable index key: %#v", idx.Key)
}
}
}
return nil
} Type guard
func isKnownIndexStep(s cty.PathStep) bool {
idx, ok := s.(cty.IndexStep)
if !ok {
return true
}
return idx.Key.IsKnown() && !idx.Key.IsNull()
} Try / catch
raw, err := jsonplan.MarshalChanges(changes)
if err != nil && strings.Contains(err.Error(), "Failed to marshal index step key") {
// surface a clearer error pointing at the for_each/count config
return fmt.Errorf("cannot JSON-encode plan: a sensitive/changed value sits at an index path with an unencodable key; simplify for_each keys: %w", err)
} Prevention
- Prefer string/number for_each keys over complex object keys.
- Keep Terraform and ctyjson current; later versions encode more key types.
- When a sensitive value is nested under a dynamic index, test `terraform plan -json` early to catch encoding failures.
When it happens
Trigger: A resource uses for_each/count and a sensitive value sits at an indexed path whose key is null, unknown, or of a type ctyjson rejects; a provider emits an attribute path with an unusual index key type.
Common situations: for_each over a map with sensitive nested attributes; plan output (-json) for a configuration combining dynamic blocks with sensitive markers; provider schema returning paths with object/tuple-typed index keys.
Related errors
- Failed to marshal get attr step name %#v
- Unsupported path step %#v (%t)
- cannot decode tfvars from a null value
- could not decode output
- could not interpret output
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/02ac1c1de9dcf8e1.
Report an issue: GitHub.
Appendix: source
Thrown at internal/command/jsonplan/plan.go:1072
for _, path := range pathList {
jsonPath, err := encodePath(path)
if err != nil {
return nil, err
}
jsonPaths = append(jsonPaths, jsonPath)
}
return json.Marshal(jsonPaths)
}
func encodePath(path cty.Path) (json.RawMessage, error) {
steps := make([]json.RawMessage, 0, len(path))
for _, step := range path {
switch s := step.(type) {
case cty.IndexStep:
key, err := ctyjson.Marshal(s.Key, s.Key.Type())
if err != nil {
return nil, fmt.Errorf("Failed to marshal index step key %#v: %s", s.Key, err)
}
steps = append(steps, key)
case cty.GetAttrStep:
name, err := json.Marshal(s.Name)
if err != nil {
return nil, fmt.Errorf("Failed to marshal get attr step name %#v: %s", s.Name, err)
}
steps = append(steps, name)
default:
return nil, fmt.Errorf("Unsupported path step %#v (%t)", step, step)
}
}
return json.Marshal(steps)
}
View on GitHub (pinned to d32a084675)