hashicorp/packer · error

Error opening shell script: %s

Error message

Error opening shell script: %s

What it means

Raised in the windows-shell provisioner's Provision when a script file selected for provisioning cannot be opened on the host with os.Open. Provision resolves the script list (inline-generated temp script or configured `scripts`), then opens each file to upload it to the guest; any open failure is wrapped as "Error opening shell script: %s" with the original OS error. Unlike the Prepare-time stat check, this runs during the build, so paths that vanished or became unreadable between validation and provisioning land here.

Source

Thrown at provisioner/windows-shell/provisioner.go:187

	p.generatedData = generatedData

	if p.config.Inline != nil {
		temp, err := extractScript(p)
		if err != nil {
			ui.Error(fmt.Sprintf("Unable to extract inline scripts into a file: %s", err))
		}
		scripts = append(scripts, temp)
		// Remove temp script containing the inline commands when done
		defer os.Remove(temp)
	}

	for _, path := range scripts {
		ui.Say(fmt.Sprintf("Provisioning with shell script: %s", path))

		log.Printf("Opening %s for reading", path)
		f, err := os.Open(path)
		if err != nil {
			return fmt.Errorf("Error opening shell script: %s", err)
		}
		defer f.Close()

		// Create environment variables to set before executing the command
		flattenedVars := p.createFlattenedEnvVars()

		// Compile the command
		p.config.ctx.Data = &ExecuteCommandTemplate{
			Vars: flattenedVars,
			Path: p.config.RemotePath,
		}
		command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
		if err != nil {
			return fmt.Errorf("Error processing command: %s", err)
		}

		// Upload the file and run the command. Do this in the context of
		// a single retryable function so that we don't end up with

View on GitHub (pinned to eb36e3c3e4)

Solutions

  1. Re-check the path exists at build time (not just validation): `ls -l <path>` immediately before `packer build` and use `${path.root}` in HCL2 to make paths absolute.
  2. Fix file permissions so the user running Packer can read the script.
  3. Ensure no earlier provisioner or cleanup step deletes the script directory mid-build.
  4. Re-run `packer validate` after any file moves so validation catches it up front.

Example fix

// before: relative path breaks when cwd differs
scripts = ["setup.bat"]
// after
scripts = ["${path.root}/scripts/setup.bat"]
Defensive patterns

Strategy: validation

Validate before calling

for _, s := range scripts {
    f, err := os.Open(s)
    if err != nil { return fmt.Errorf("pre-open check failed for %q: %w", s, err) }
    f.Close()
}

Try / catch

if strings.Contains(err.Error(), "Error opening shell script") {
    // fail fast before guest work; do not retry cloud-side
    return fmt.Errorf("host-side script unreadable: %w", err)
}

Prevention

When it happens

Trigger: Provision loops over `scripts` and calls os.Open(path) for each; the call returns an error because the file was deleted or renamed after Prepare, permissions changed, or the path is a directory — provisioner.go:185-189. Also triggered for the temp script path if extractScript output was removed mid-build.

Common situations: Script deleted by a prior build step or tmp cleaner between validation and provisioning; relative path broken because the build changed working directory (e.g. running packer from a different cwd than intended); permissions tightened on a shared CI workspace; antivirus locking/removing the file.

Related errors


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