{"record":{"id":"e05876cafda61476","repo":"opentofu/opentofu","slug":"the-specified-plugin-cache-dir-s-cannot-be-opened","errorCode":null,"errorMessage":"The specified plugin cache dir %s cannot be opened: %w","messagePattern":"The specified plugin cache dir (.+?) cannot be opened: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/command/cliconfig/cliconfig.go","lineNumber":407,"sourceCode":"\tif len(c.OCIRepositoryCredentials) != 0 {\n\t\tseenOCICredentialsAddrs := make(map[string]struct{})\n\t\tfor _, creds := range c.OCIRepositoryCredentials {\n\t\t\tif _, ok := seenOCICredentialsAddrs[creds.RepositoryPrefix]; ok {\n\t\t\t\tdiags = diags.Append(\n\t\t\t\t\t//nolint:stylecheck // Despite typical Go idiom, our existing precedent here is to return full sentences suitable for inclusion in diagnostics.\n\t\t\t\t\tfmt.Errorf(\"Duplicate oci_credentials block for %q\", creds.RepositoryPrefix),\n\t\t\t\t)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tseenOCICredentialsAddrs[creds.RepositoryPrefix] = struct{}{}\n\t\t}\n\t}\n\n\tif c.PluginCacheDir != \"\" {\n\t\t_, err := os.Stat(c.PluginCacheDir)\n\t\tif err != nil {\n\t\t\tdiags = diags.Append(\n\t\t\t\tfmt.Errorf(\"The specified plugin cache dir %s cannot be opened: %w\", c.PluginCacheDir, err),\n\t\t\t)\n\t\t}\n\t}\n\n\treturn diags\n}\n\n// Merge merges two configurations and returns a third entirely\n// new configuration with the two merged.\nfunc (c *Config) Merge(c2 *Config) *Config {\n\tvar result Config\n\n\tresult.PluginCacheDir = c.PluginCacheDir\n\tif result.PluginCacheDir == \"\" {\n\t\tresult.PluginCacheDir = c2.PluginCacheDir\n\t}\n\n\tif c.PluginCacheMayBreakDependencyLockFile || c2.PluginCacheMayBreakDependencyLockFile {","sourceCodeStart":389,"sourceCodeEnd":425,"githubUrl":"https://github.com/opentofu/opentofu/blob/3561785c48c1ce615e7c50261bd351f26053efa2/internal/command/cliconfig/cliconfig.go#L389-L425","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nplugin_cache_dir = \"~/.tofu.d/plugin-cache\"\n\n// after\nplugin_cache_dir = \"${HOME}/.tofu.d/plugin-cache\"\n// and once in a shell: mkdir -p ~/.tofu.d/plugin-cache","handlingStrategy":"validation","validationCode":"func pluginCacheDirUsable(dir string) error {\n\tif dir == \"\" {\n\t\treturn nil\n\t}\n\tinfo, err := os.Stat(os.ExpandEnv(dir)) // mirrors loader behavior\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !info.IsDir() {\n\t\treturn fmt.Errorf(\"%s is not a directory\", dir)\n\t}\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["plugin-cache","filesystem","cli-config","validation"],"backgroundTag":null,"analyzedSha":"3561785c48c1ce615e7c50261bd351f26053efa2","analyzedAt":"2026-08-15T23:27:16.226Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}