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

  1. Ensure the provisioner block has exactly one of source/content resolving to a concrete value at apply time.
  2. Run terraform validate before apply so the validation-time check catches the missing attribute first.
  3. 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

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


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)