hashicorp/terraform · error

The specified plugin cache dir

Error message

The specified plugin cache dir %s cannot be opened: %s

What it means

Thrown when os.Stat fails on the configured plugin_cache_dir path. Terraform validates that the plugin cache directory exists and is accessible so that provider downloads can be cached there. The error includes the configured path and the underlying stat error.

Solutions

  1. Create the directory: mkdir -p <plugin_cache_dir>
  2. Check permissions: chmod 755 <plugin_cache_dir>
  3. Use an absolute path for plugin_cache_dir in the config
  4. Remove or comment out plugin_cache_dir if you do not need caching

Example fix

// before
plugin_cache_dir = "~/terraform-plugins"

// after
plugin_cache_dir = "/home/user/.terraform.d/plugin-cache"
Defensive patterns

Strategy: validation

Validate before calling

// Verify plugin cache dir exists and is a directory before running terraform
func checkPluginCacheDir(path string) error {
    if path == "" {
        return nil // not configured, nothing to check
    }
    info, err := os.Stat(path)
    if err != nil {
        return fmt.Errorf("plugin cache dir %s not accessible: %w", path, err)
    }
    if !info.IsDir() {
        return fmt.Errorf("plugin cache path %s is not a directory", path)
    }
    return nil
}

Prevention

When it happens

Trigger: c.PluginCacheDir is non-empty but os.Stat(c.PluginCacheDir) returns an error: the directory does not exist, permissions are insufficient, or the path is invalid.

Common situations: plugin_cache_dir set to a nonexistent path; directory deleted after config was written; permissions changed; path copied from a different machine/environment; relative path resolved against an unexpected working directory.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/be27ff8189618ab4. Report an issue: GitHub.

Appendix: source

Thrown at internal/command/cliconfig/cliconfig.go:331

	// Should have zero or one "credentials_helper" blocks
	if len(c.CredentialsHelpers) > 1 {
		diags = diags.Append(
			fmt.Errorf("No more than one credentials_helper block may be specified"),
		)
	}

	// Should have zero or one "provider_installation" blocks
	if len(c.ProviderInstallation) > 1 {
		diags = diags.Append(
			fmt.Errorf("No more than one provider_installation block may be specified"),
		)
	}

	if c.PluginCacheDir != "" {
		_, err := os.Stat(c.PluginCacheDir)
		if err != nil {
			diags = diags.Append(
				fmt.Errorf("The specified plugin cache dir %s cannot be opened: %s", c.PluginCacheDir, err),
			)
		}
	}

	return diags
}

// Merge merges two configurations and returns a third entirely
// new configuration with the two merged.
func (c *Config) Merge(c2 *Config) *Config {
	var result Config
	result.Providers = make(map[string]string)
	result.Provisioners = make(map[string]string)
	maps.Copy(result.Providers, c.Providers)
	maps.Copy(result.Provisioners, c.Provisioners)
	for k, v := range c2.Providers {
		if v1, ok := c.Providers[k]; ok {
			log.Printf("[INFO] Local %s provider configuration '%s' overrides '%s'", k, v, v1)

View on GitHub (pinned to d32a084675)