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

  1. 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).
  2. Run `ls`/`test -f <path>` on the host to confirm the file exists and is readable before the build.
  3. 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.
  4. 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

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


AI-assisted analysis of hashicorp/packer@eb36e3c3e4 (2026-09-05). Data as JSON: /api/errors/1693698478333752. Report an issue: GitHub.