hashicorp/terraform · error

cannot search

Error message

cannot search %s: %s

What it means

`SearchLocalDirectory` walks `baseDir` with `filepath.Walk`. The walk callback receives an `err` argument when the filesystem cannot descend into a path (permission denied, broken symlink, removed directory). That error is wrapped with the offending `fullPath` and surfaced. It is the catch-all for filesystem-level failures during local plugin discovery.

Solutions

  1. Check the `fullPath` named in the error and `ls -l` / `stat` it for permissions or symlink breakage.
  2. Repair or remove broken symlinks: `find ~/.terraform.d/plugins -xtype l -delete`.
  3. Fix ownership/permissions so the running user can traverse the directory (chmod/ chown).
  4. Point `plugin_directory` in the CLI config at a clean, readable directory and re-run.

Example fix

// before: broken symlink in plugin dir causes walk error
$ terraform init
Error: cannot search /home/u/.terraform.d/plugins/registry/old: no such file or directory

// after: prune dead links
$ find ~/.terraform.d/plugins -xtype l -print -delete
$ terraform init
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check the plugin directory is traversable
if fi, err := os.Stat(baseDir); err != nil || !fi.IsDir() {
    return nil, fmt.Errorf("plugin dir unusable: %w", err)
}
// Optionally prune dead symlinks before walking
filepath.Walk(baseDir, func(p string, info os.FileInfo, err error) error {
    if err != nil { return err }
    if info.Mode()&os.ModeSymlink != 0 {
        if _, e := os.Stat(p); e != nil { os.Remove(p) }
    }
    return nil
})

Try / catch

// Recover from per-path walk errors and continue discovery
err := filepath.Walk(baseDir, func(fullPath string, info os.FileInfo, err error) error {
    if err != nil {
        log.Printf("[WARN] skipping unreadable plugin path %s: %s", fullPath, err)
        return nil // skip this entry, keep walking
    }
    // ...normal handling...
    return nil
})

Prevention

When it happens

Trigger: `filepath.Walk` invokes its callback with a non-nil `err` for a given `fullPath`; the callback returns `fmt.Errorf("cannot search %s: %s", fullPath, err)` at filesystem_search.go:51.

Common situations: Permission denied on a directory under `~/.terraform.d/plugins` or `-plugin-dir`; broken symlink in the plugin path; directory deleted during the walk; filesystem mounted read-only or NFS hiccup; stale leftover from a failed install.

Related errors


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

Appendix: source

Thrown at internal/getproviders/filesystem_search.go:51

	// potentially keep their plugins in a non-standard location but use a
	// symlink to help Terraform find them anyway.
	originalBaseDir := baseDir
	if finalDir, err := filepath.EvalSymlinks(baseDir); err == nil {
		if finalDir != filepath.Clean(baseDir) {
			log.Printf("[TRACE] getproviders.SearchLocalDirectory: using %s instead of %s", finalDir, baseDir)
		}
		baseDir = finalDir
	} else {
		// We'll eat this particular error because if we're somehow able to
		// find plugins via baseDir below anyway then we'd rather do that than
		// hard fail, but we'll log it in case it's useful for diagnosing why
		// discovery didn't produce the expected outcome.
		log.Printf("[TRACE] getproviders.SearchLocalDirectory: failed to resolve symlinks for %s: %s", baseDir, err)
	}

	err := filepath.Walk(baseDir, func(fullPath string, info os.FileInfo, err error) error {
		if err != nil {
			return fmt.Errorf("cannot search %s: %s", fullPath, err)
		}

		// There are two valid directory structures that we support here...
		// Unpacked: registry.terraform.io/hashicorp/aws/2.0.0/linux_amd64 (a directory)
		// Packed:   registry.terraform.io/hashicorp/aws/terraform-provider-aws_2.0.0_linux_amd64.zip (a file)
		//
		// Both of these give us enough information to identify the package
		// metadata.
		fsPath, err := filepath.Rel(baseDir, fullPath)
		if err != nil {
			// This should never happen because the filepath.Walk contract is
			// for the paths to include the base path.
			log.Printf("[TRACE] getproviders.SearchLocalDirectory: ignoring malformed path %q during walk: %s", fullPath, err)
			return nil
		}
		relPath := filepath.ToSlash(fsPath)
		parts := strings.Split(relPath, "/")

View on GitHub (pinned to d32a084675)