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
- Verify the script file path is correct relative to the Terraform execution directory with `ls -la <path>`.
- Ensure the script file is readable by the user running Terraform (check with `cat <path>`).
- Use absolute paths or paths relative to the module root, not relative to your shell's cwd.
- 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
- Use path.module-relative paths for scripts to ensure correct resolution regardless of cwd.
- Verify script files exist with a pre-flight check or local-exec before the remote-exec provisioner.
- Ensure script files are readable by the Terraform process user.
- Avoid using ~ or environment variables in script paths; expand them beforehand.
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
- Error starting script
- Failed to upload script
- Error creating temporary file for upload
- Error reading script
- invalid empty string in 'script'
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)