golangci/golangci-lint · error

create destination directory: %w

Error message

create destination directory: %w

What it means

When cfg.Destination is set, copyBinary creates it (including parents) with os.MkdirAll(..., os.ModePerm) before writing the binary. This error wraps a MkdirAll failure, meaning the destination directory tree could not be created — typically a permissions or path problem (e.g. a non-directory file exists at a path component).

Source

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

func (b Builder) copyBinary(binaryName string) error {
	src := filepath.Join(b.repo, binaryName)

	source, err := os.Open(filepath.Clean(src))
	if err != nil {
		return fmt.Errorf("open source file: %w", err)
	}

	defer func() { _ = source.Close() }()

	info, err := source.Stat()
	if err != nil {
		return fmt.Errorf("stat source file: %w", err)
	}

	if b.cfg.Destination != "" {
		err = os.MkdirAll(b.cfg.Destination, os.ModePerm)
		if err != nil {
			return fmt.Errorf("create destination directory: %w", err)
		}
	}

	dst, err := os.OpenFile(filepath.Join(b.cfg.Destination, binaryName), os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode())
	if err != nil {
		return fmt.Errorf("create destination file: %w", err)
	}

	defer func() { _ = dst.Close() }()

	_, err = io.Copy(dst, source)
	if err != nil {
		return fmt.Errorf("copy source to destination: %w", err)
	}

	return nil
}

View on GitHub (pinned to ed7a235d2d)

Solutions

  1. Check each path component of cfg.Destination — rename/remove any regular file that occupies a directory name
  2. Verify write permission on the closest existing ancestor (or run with elevated privileges / pick a writable destination)
  3. Confirm the destination volume is not mounted read-only
  4. Correct the cfg.Destination value in the configuration to an absolute, valid directory path
  5. Read the wrapped *os.SyscallError: EACCES => permissions, ENOTDIR => file-in-path, EROFS => read-only

Example fix

// before
destination: /usr/local/bin   # needs root
// after
destination: ~/go/bin         # writable without elevation
Defensive patterns

Strategy: validation

Validate before calling

if dest := cfg.Destination; dest != "" {
    if fi, err := os.Stat(dest); err == nil && !fi.IsDir() {
        return fmt.Errorf("destination %s exists and is not a directory", dest)
    }
    if err := os.MkdirAll(dest, 0o755); err != nil {
        return fmt.Errorf("cannot create destination %s: %w", dest, err)
    }
}
// also verify write access:
if dest != "" {
    f, err := os.CreateTemp(dest, ".wcheck*")
    if err != nil { return fmt.Errorf("destination %s not writable: %w", dest, err) }
    f.Close(); os.Remove(f.Name())
}

Try / catch

if err := b.copyBinary(name); err != nil {
    var perr *fs.PathError
    if errors.As(err, &perr) && errors.Is(perr, fs.ErrPermission) {
        log.Fatalf("cannot create %s: permission denied — choose a writable destination or elevate", perr.Path)
    }
    return err
}

Prevention

When it happens

Trigger: cfg.Destination is non-empty and os.MkdirAll fails: parent path component is an existing regular file, no write permission on an ancestor directory, read-only filesystem, or an invalid/over-long path.

Common situations: Destination configured as an existing file instead of a directory, installing to /usr/local/bin without sudo, HOME misconfigured in CI containers, or a typo'd destination path landing on a read-only mount.

Understand the failure class

Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.

Related errors


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