golangci/golangci-lint · error

hash plugin directory: %w

Error message

hash plugin directory: %w

What it means

createVersion computes a deterministic version hash by hashing each configured plugin's directory with hashDir(plugin.Path, "", dirhash.DefaultHash). This error wraps a hashDir failure, meaning the plugin directory could not be read or hashed (module dirhash requires reading all files). It aborts version computation for the build.

Source

Thrown at pkg/commands/internal/builder.go:263

	name := b.cfg.Name
	if runtime.GOOS == "windows" {
		name += ".exe"
	}

	return name
}

func (b Builder) createVersion(orig string) (string, error) {
	hash := sha256.New()

	for _, plugin := range b.cfg.Plugins {
		if plugin.Path == "" {
			continue
		}

		dh, err := hashDir(plugin.Path, "", dirhash.DefaultHash)
		if err != nil {
			return "", fmt.Errorf("hash plugin directory: %w", err)
		}

		b.log.Infof("%s: %s", plugin.Path, dh)

		hash.Write([]byte(dh))
	}

	return fmt.Sprintf("%s-custom-gcl-%s",
		sanitizeVersion(orig),
		sanitizeVersion(base64.URLEncoding.EncodeToString(hash.Sum(nil))),
	), nil
}

func sanitizeVersion(v string) string {
	fn := func(c rune) bool {
		return !unicode.IsLetter(c) && !unicode.IsNumber(c) && c != '.' && c != '/'
	}

View on GitHub (pinned to ed7a235d2d)

Solutions

  1. Verify each plugin.Path exists and is a directory (ls <plugin.Path>) as configured
  2. Initialize missing submodules/vendored deps: git submodule update --init --recursive
  3. Fix the plugin path in configuration or restore the plugin checkout at the expected location
  4. Check read permissions on the plugin directory and its files
  5. The wrapped error names the specific file that failed — open/read that path directly to pinpoint the cause

Example fix

// before
plugins:
  - path: ./plugins/myplugin   # never cloned
// after
git submodule update --init --recursive
# or fix path:
plugins:
  - path: ../shared/myplugin
Defensive patterns

Strategy: validation

Validate before calling

for _, plugin := range cfg.Plugins {
    if plugin.Path == "" { continue }
    fi, err := os.Stat(plugin.Path)
    if err != nil {
        return fmt.Errorf("plugin dir %s missing (init submodules?): %w", plugin.Path, err)
    }
    if !fi.IsDir() {
        return fmt.Errorf("plugin path %s is not a directory", plugin.Path)
    }
}

Try / catch

dh, err := hashDir(plugin.Path, "", dirhash.DefaultHash)
if err != nil {
    var perr *fs.PathError
    if errors.As(err, &perr) {
        log.Fatalf("cannot hash plugin %s: %v — restore the checkout (git submodule update --init --recursive) or fix the configured path", plugin.Path, perr.Err)
    }
    return err
}

Prevention

When it happens

Trigger: A plugin entry in the configuration has a non-empty Path but the directory is missing, unreadable, or contains unreadable files; hashDir walks the tree and fails on open/read/stat of any file, or the path is a file rather than a directory.

Common situations: Plugin checked out at a tag/branch that no longer exists after switching versions, submodule not initialized (plugins/vendor/empty), path typo in config, or checkout on CI with sparse/partial clone excluding the plugin directory.

Related errors


AI-assisted analysis of golangci/golangci-lint@ed7a235d2d (2026-09-02). Data as JSON: /api/errors/a58d4083dc9ee199. Report an issue: GitHub.