hashicorp/terraform · error
source and content cannot both be null
Error message
source and content cannot both be null
What it means
Defensive runtime error inside the file provisioner's source/content expansion helper (resource_provisioner.go:156, the `default` arm). After validating at config time, the provisioner expands the source path (homedir.Expand) or returns the content blob; if execution reaches the default branch both inputs were null at runtime, which should be unreachable given ValidateProvisionerConfig. Seeing it means validation was bypassed or the coerced config differs at runtime.
Solutions
- Ensure the provisioner block has exactly one of source/content resolving to a concrete value at apply time.
- Run terraform validate before apply so the validation-time check catches the missing attribute first.
- If a variable supplies the value, give it a non-null default or guard the provisioner with a count/for_each condition.
Example fix
# before
variable "app_conf" { type = string }
provisioner "file" {
content = var.app_conf # null when unset
destination = "/etc/app/app.conf"
}
# after
variable "app_conf" { type = string }
provisioner "file" {
source = "./app.conf" # concrete value
destination = "/etc/app/app.conf"
} Defensive patterns
Strategy: validation
Validate before calling
// Ensure at least one payload source is non-null at runtime before calling ProvisionResource.
if source.IsNull() && content.IsNull() {
return errors.New("source and content cannot both be null")
} Type guard
func hasRuntimePayload(src, content cty.Value) bool {
return !src.IsNull() || !content.IsNull()
} Prevention
- Run terraform validate so the validation-time guard fires before apply.
- Guard the provisioner with count/for_each so it only runs when payload is concrete.
- Avoid conditionally-null source/content values.
When it happens
Trigger: ProvisionResource is invoked with a config where both `source` and `content` evaluate to null at runtime (e.g. via dynamic/conditional values), skipping the validation-time guard and reaching the default branch of the switch.
Common situations: Content/source coming from a variable, local, or data source that evaluates to null under certain conditions. A third-party harness calling the provisioner gRPC plugin directly without running ValidateProvisionerConfig.
Related errors
- Cannot set both 'source' and 'content'
- Must provide one of 'source' or 'content'
- host for provisioner cannot be empty
- invalid empty string in 'script'
- invalid empty string in 'scripts'
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/e14d3395e03c1fcb.
Report an issue: GitHub.
Appendix: source
Thrown at internal/builtin/provisioners/file/resource_provisioner.go:156
file, err := os.CreateTemp("", "tf-file-content")
if err != nil {
return "", true, err
}
if _, err = file.WriteString(content.AsString()); err != nil {
file.Close()
return "", true, err
}
file.Close()
return file.Name(), true, nil
case !src.IsNull():
expansion, err := homedir.Expand(src.AsString())
return expansion, false, err
default:
return "", false, errors.New("source and content cannot both be null")
}
}
// copyFiles is used to copy the files from a source to a destination
func copyFiles(ctx context.Context, comm communicator.Communicator, src, dst string) error {
retryCtx, cancel := context.WithTimeout(ctx, comm.Timeout())
defer cancel()
// Wait and retry until we establish the connection
err := communicator.Retry(retryCtx, func() error {
return comm.Connect(nil)
})
if err != nil {
return err
}
// disconnect when the context is canceled, which will close this after
// Apply as well.View on GitHub (pinned to d32a084675)