opentofu/opentofu · error
The specified plugin cache dir %s cannot be opened: %w
Error message
The specified plugin cache dir %s cannot be opened: %w
What it means
If plugin_cache_dir is set (file or TF_PLUGIN_CACHE_DIR, with os.ExpandEnv applied), validation stats the directory to prove it is usable; any os.Stat failure is wrapped as 'The specified plugin cache dir <dir> cannot be opened'. OpenTofu does not create the directory for you, so a missing path is the most common cause.
Source
Thrown at internal/command/cliconfig/cliconfig.go:407
if len(c.OCIRepositoryCredentials) != 0 {
seenOCICredentialsAddrs := make(map[string]struct{})
for _, creds := range c.OCIRepositoryCredentials {
if _, ok := seenOCICredentialsAddrs[creds.RepositoryPrefix]; ok {
diags = diags.Append(
//nolint:stylecheck // Despite typical Go idiom, our existing precedent here is to return full sentences suitable for inclusion in diagnostics.
fmt.Errorf("Duplicate oci_credentials block for %q", creds.RepositoryPrefix),
)
continue
}
seenOCICredentialsAddrs[creds.RepositoryPrefix] = struct{}{}
}
}
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: %w", 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.PluginCacheDir = c.PluginCacheDir
if result.PluginCacheDir == "" {
result.PluginCacheDir = c2.PluginCacheDir
}
if c.PluginCacheMayBreakDependencyLockFile || c2.PluginCacheMayBreakDependencyLockFile {View on GitHub (pinned to 3561785c48)
Solutions
- Create the directory and make it writable: mkdir -p <dir> (then confirm with ls -ld)
- Replace ~ with an absolute path or a ${HOME} reference, since only $VAR/${VAR} expansion happens
- If set via TF_PLUGIN_CACHE_DIR, echo it in your shell/CI to verify the resolved value
Example fix
// before
plugin_cache_dir = "~/.tofu.d/plugin-cache"
// after
plugin_cache_dir = "${HOME}/.tofu.d/plugin-cache"
// and once in a shell: mkdir -p ~/.tofu.d/plugin-cache Defensive patterns
Strategy: validation
Validate before calling
func pluginCacheDirUsable(dir string) error {
if dir == "" {
return nil
}
info, err := os.Stat(os.ExpandEnv(dir)) // mirrors loader behavior
if err != nil {
return err
}
if !info.IsDir() {
return fmt.Errorf("%s is not a directory", dir)
}
return nil
} Prevention
- Pre-create the cache dir in provisioning: mkdir -p + correct ownership
- Never use ~ in plugin_cache_dir; ExpandEnv only understands $VAR/${VAR}
- Validate TF_PLUGIN_CACHE_DIR in CI before invoking tofu init
When it happens
Trigger: plugin_cache_dir points at a path that does not exist, is unreadable by the current user, or is a dangling symlink; the value contains '~' which ExpandEnv does not expand; an env var referenced via ${VAR} is empty, producing a wrong path.
Common situations: Copying plugin_cache_dir = "~/.terraform.d/plugin-cache" from docs (tilde is not expanded); setting TF_PLUGIN_CACHE_DIR in CI where the cache volume is mounted elsewhere or with wrong ownership; read-only cache dir owned by root.
Related errors
- The host %q block has an invalid hostname: %w
- The credentials %q block has an invalid hostname: %w
- No more than one credentials_helper block may be specified
- No more than one provider_installation block may be specifie
- No more than one oci_default_credentials block may be specif
AI-assisted analysis of opentofu/opentofu@3561785c48 (2026-08-15).
Data as JSON: /api/errors/e05876cafda61476.
Report an issue: GitHub.