{"record":{"id":"be27ff8189618ab4","repo":"hashicorp/terraform","slug":"the-specified-plugin-cache-dir-s-cannot-be-opened","errorCode":null,"errorMessage":"The specified plugin cache dir %s cannot be opened: %s","messagePattern":"The specified plugin cache dir (.+?) cannot be opened: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/command/cliconfig/cliconfig.go","lineNumber":331,"sourceCode":"\t// Should have zero or one \"credentials_helper\" blocks\n\tif len(c.CredentialsHelpers) > 1 {\n\t\tdiags = diags.Append(\n\t\t\tfmt.Errorf(\"No more than one credentials_helper block may be specified\"),\n\t\t)\n\t}\n\n\t// Should have zero or one \"provider_installation\" blocks\n\tif len(c.ProviderInstallation) > 1 {\n\t\tdiags = diags.Append(\n\t\t\tfmt.Errorf(\"No more than one provider_installation block may be specified\"),\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: %s\", 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\tresult.Providers = make(map[string]string)\n\tresult.Provisioners = make(map[string]string)\n\tmaps.Copy(result.Providers, c.Providers)\n\tmaps.Copy(result.Provisioners, c.Provisioners)\n\tfor k, v := range c2.Providers {\n\t\tif v1, ok := c.Providers[k]; ok {\n\t\t\tlog.Printf(\"[INFO] Local %s provider configuration '%s' overrides '%s'\", k, v, v1)","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/hashicorp/terraform/blob/c9def3e214014c1188faabfc4a5bde5095139765/internal/command/cliconfig/cliconfig.go#L313-L349","documentation":"Emitted by `Config.Validate` (cliconfig.go:327-332) when `os.Stat(c.PluginCacheDir)` fails for a non-empty `plugin_cache_dir`. The directory must already exist and be stat-able; Terraform does not create it. The `%s` values are the configured dir and the OS error. The value can come from `plugin_cache_dir` in config or the `TF_PLUGIN_CACHE_DIR` env var.","triggerScenarios":"Setting `plugin_cache_dir` (or `TF_PLUGIN_CACHE_DIR`) to a path that does not exist, is not a directory, or is not accessible, then running any Terraform command that loads CLI config.","commonSituations":"Pointing the cache at a not-yet-created directory; a path on a disconnected mount; permission denied on a shared cache dir; typo in the path.","solutions":["Create the directory first: `mkdir -p <path>`.","Ensure the directory is writable by the user running Terraform.","If unset is intended, remove `plugin_cache_dir` from config and unset `TF_PLUGIN_CACHE_DIR`.","Fix typos in the configured path."],"exampleFix":"# before\nexport TF_PLUGIN_CACHE_DIR=/opt/tfplugin-cache   # does not exist\nterraform init\n# The specified plugin cache dir /opt/tfplugin-cache cannot be opened: stat: no such file or directory\n\n# after\nmkdir -p /opt/tfplugin-cache\nterraform init","handlingStrategy":"validation","validationCode":"// Ensure the plugin cache dir exists and is writable before running.\nfunc pluginCacheOK(dir string) error {\n    if dir == \"\" {\n        return nil\n    }\n    fi, err := os.Stat(dir)\n    if err != nil {\n        return fmt.Errorf(\"plugin cache dir %s unusable: %w\", dir, err)\n    }\n    if !fi.IsDir() {\n        return fmt.Errorf(\"%s is not a directory\", dir)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["`mkdir -p` the cache dir before setting `TF_PLUGIN_CACHE_DIR`.","Keep the cache dir writable by all users/CI runners that share it.","Unset the variable if the cache is not intended."],"tags":["terraform","cli-config","plugin-cache","validation","io"],"analyzedSha":"c9def3e214014c1188faabfc4a5bde5095139765","analyzedAt":"2026-08-07T15:39:49.278Z","schemaVersion":2},"datasetVersion":"2026-08-07T21:17:07.882Z"}