hashicorp/terraform · error

Failed to open script

Error message

Failed to open script '%s': %v

What it means

The remote-exec provisioner's collectScripts function fails to open a local script file referenced in the 'script' or 'scripts' attribute. After collecting script paths from the config, it iterates and calls os.Open on each. If any file cannot be opened, it closes all previously opened handles and returns this error.

Solutions

  1. Verify the script file path is correct relative to the Terraform execution directory with `ls -la <path>`.
  2. Ensure the script file is readable by the user running Terraform (check with `cat <path>`).
  3. Use absolute paths or paths relative to the module root, not relative to your shell's cwd.
  4. If using a list in 'scripts', validate every entry exists before running apply.

Example fix

# before — relative path that may not resolve
provisioner "remote-exec" {
  scripts = ["~/deploy.sh", "../scripts/setup.sh"]
}

# after — use path.module for reliable resolution
provisioner "remote-exec" {
  scripts = ["${path.module}/scripts/deploy.sh", "${path.module}/scripts/setup.sh"]
}
Defensive patterns

Strategy: validation

Validate before calling

// Before running remote-exec, verify all script files exist
for _, scriptPath := range scriptPaths {
    info, err := os.Stat(scriptPath)
    if err != nil {
        return fmt.Errorf("script file %s not found: %w", scriptPath, err)
    }
    if info.IsDir() {
        return fmt.Errorf("script path %s is a directory, not a file", scriptPath)
    }
}

Prevention

When it happens

Trigger: Using a remote-exec provisioner with 'script' or 'scripts' attributes pointing to a file that does not exist or is not readable. The os.Open call at resource_provisioner.go:216 fails with a filesystem error (ENOENT, EACCES, etc.).

Common situations: Typo in the script path relative to the Terraform working directory. Script file exists but has restrictive permissions (not readable by the Terraform process). Running 'terraform apply' from a different working directory than where the scripts are located. Script path contains unexpanded environment variables or ~.

Related errors


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

Appendix: source

Thrown at internal/builtin/provisioners/remote-exec/resource_provisioner.go:221

				return nil, errors.New("invalid null string in 'script'")
			}
			s := script.AsString()
			if s == "" {
				return nil, errors.New("invalid empty string in 'script'")
			}
			scripts = append(scripts, s)
		}
	}

	// Open all the scripts
	var fhs []io.ReadCloser
	for _, s := range scripts {
		fh, err := os.Open(s)
		if err != nil {
			for _, fh := range fhs {
				fh.Close()
			}
			return nil, fmt.Errorf("Failed to open script '%s': %v", s, err)
		}
		fhs = append(fhs, fh)
	}

	// Done, return the file handles
	return fhs, nil
}

// runScripts is used to copy and execute a set of scripts
func runScripts(ctx context.Context, o provisioners.UIOutput, comm communicator.Communicator, scripts []io.ReadCloser) 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(o)
	})
	if err != nil {

View on GitHub (pinned to d32a084675)