hashicorp/packer · error

Error opening shell script: %s

Error message

Error opening shell script: %s

What it means

Provision iterates over the resolved script paths (inline-generated temp scripts plus configured script files) and opens each locally to upload it (provisioner/shell/provisioner.go:302). This error means os.Open(path) on a script file failed — the file does not exist at that path, is unreadable, or the path is a directory. For temp inline scripts this indicates the temp file was removed early; for user scripts it is almost always a bad path in the template.

Source

Thrown at provisioner/shell/provisioner.go:302

			cmd.Wait()
			p.config.envVarFile = remoteVFName
			return nil
		})
		if err != nil {
			return err
		}
	}

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

	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()

		// Compile the command
		// These are extra variables that will be made available for interpolation.
		generatedData["Vars"] = flattenedEnvVars
		generatedData["EnvVarFile"] = p.config.envVarFile
		generatedData["Path"] = p.config.RemotePath
		p.config.ctx.Data = generatedData

		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
		// the case that the upload succeeded, a restart is initiated,

View on GitHub (pinned to eb36e3c3e4)

Solutions

  1. Verify each path in scripts exists relative to where packer is invoked (ls -l <path>).
  2. Use absolute paths or correct the working directory in CI before running packer.
  3. Ensure script files are committed/copied into the CI workspace.
  4. Fix file permissions (chmod +r) or replace broken symlinks.
  5. On case-sensitive filesystems, match the filename case exactly.

Example fix

// before
provisioner "shell" {
  scripts = ["./scripts/setup.sh"]
}
// after — path verified from the template's directory
provisioner "shell" {
  scripts = ["${path.root}/scripts/setup.sh"]
}
Defensive patterns

Strategy: validation

Validate before calling

// validate all script paths before invoking packer
for f in scripts/setup.sh scripts/bootstrap.sh; do
  [ -f "$f" ] && [ -r "$f" ] || { echo "missing/unreadable script: $f"; exit 1; }
done
packer build template.pkr.hcl

Try / catch

// if invoking provisioner programmatically
f, err := os.Open(path)
if err != nil {
    if os.IsNotExist(err) {
        return fmt.Errorf("script %q not found; check scripts[] paths relative to cwd", path)
    }
    return err
}

Prevention

When it happens

Trigger: script or scripts entries point to files that don't exist relative to the packer run directory; the file exists on the machine where the template was edited but not where packer runs (CI checkout mismatch); a symlink target is missing; permissions deny read.

Common situations: Wrong relative path in the shell provisioner's scripts list; running packer from a different working directory than expected in CI; files not checked into the repo; Windows path separators vs Unix; case-sensitivity mismatch on Linux.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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