hashicorp/packer · error
Bad script '%s': %s
Error message
Bad script '%s': %s
What it means
Raised in the windows-shell provisioner's Prepare when a configured script file cannot be stat'ed on the host. Prepare validates each path in the 'scripts' array with os.Stat before the build runs; if a path is missing, unreadable, or is a directory, the provisioner appends this error so the build fails fast at validation time. The wrapped os error ('no such file or directory', 'permission denied', etc.) is included in the message.
Source
Thrown at provisioner/windows-shell/provisioner.go:123
errors.New("Only one of script or scripts can be specified."))
}
if p.config.Script != "" {
p.config.Scripts = []string{p.config.Script}
}
if len(p.config.Scripts) == 0 && p.config.Inline == nil {
errs = packersdk.MultiErrorAppend(errs,
errors.New("Either a script file or inline script must be specified."))
} else if len(p.config.Scripts) > 0 && p.config.Inline != nil {
errs = packersdk.MultiErrorAppend(errs,
errors.New("Only a script file or an inline script can be specified, not both."))
}
for _, path := range p.config.Scripts {
if _, err := os.Stat(path); err != nil {
errs = packersdk.MultiErrorAppend(errs,
fmt.Errorf("Bad script '%s': %s", path, err))
}
}
// Do a check for bad environment variables, such as '=foo', 'foobar'
for _, kv := range p.config.Vars {
vs := strings.SplitN(kv, "=", 2)
if len(vs) != 2 || vs[0] == "" {
errs = packersdk.MultiErrorAppend(errs,
fmt.Errorf("Environment variable not in format 'key=value': %s", kv))
}
}
return errs
}
// This function takes the inline scripts, concatenates them
// into a temporary file and returns a string containing the location
// of said file.View on GitHub (pinned to eb36e3c3e4)
Solutions
- Correct the `scripts` path in the template so it points to an existing file relative to where `packer build` runs (use path.root in HCL2 to anchor paths).
- Run `ls`/`test -f <path>` on the host to confirm the file exists and is readable before the build.
- If both `scripts` and `inline` are intended, note this error path only covers scripts — but ensure you don't accidentally leave an empty/invalid scripts entry alongside inline.
- Check file permissions if the path exists but the Packer user cannot stat/read it.
Example fix
// before (HCL2): file does not exist relative to cwd
provisioner "windows-shell" {
scripts = ["scripts/install.ps1"]
}
// after: anchor to template directory
provisioner "windows-shell" {
scripts = ["${path.root}/scripts/install.ps1"]
} Defensive patterns
Strategy: validation
Validate before calling
for _, s := range scripts {
info, err := os.Stat(s)
if err != nil { return fmt.Errorf("script %q missing: %w", s, err) }
if info.IsDir() { return fmt.Errorf("script %q is a directory", s) }
} Try / catch
// Prepare fails the build before any infra is created; capture and report
if err := run("packer", "validate", tpl); err != nil {
if strings.Contains(errOutput, "Bad script") { fixScriptPaths(tpl) }
} Prevention
- Anchor script paths with ${path.root} (HCL2) so they resolve regardless of cwd.
- Run `packer validate` in CI before any build job.
- Keep scripts in the repo and verify checkout/submodules in CI.
- Never point `scripts` at directories; list files explicitly.
When it happens
Trigger: Calling Prepare (via `packer build` template validation) when any entry in the provisioner's `scripts` list points to a nonexistent file, a directory instead of a file, or a file the Packer process cannot access (os.Stat returns err) — windows-shell/provisioner.go:122-127.
Common situations: Typo or wrong relative path in the template ('scripts': ["./scripts/setup.ps1"] run from a different working directory); CI checkout missing the script (submodule not fetched); file deleted or renamed after the template was written; Windows path written with wrong separators/case on a Linux host.
Related errors
- Environment variable not in format 'key=value': %s
- Error opening shell script: %s
- Only one of script or scripts can be specified.
- Either a script file or inline script must be specified.
- `max_retries` must be a valid integer: %s
AI-assisted analysis of hashicorp/packer@eb36e3c3e4 (2026-09-05).
Data as JSON: /api/errors/1693698478333752.
Report an issue: GitHub.