golangci/golangci-lint · error

create destination file: %w

Error message

create destination file: %w

What it means

copyBinary creates the destination file with os.OpenFile(filepath.Join(b.cfg.Destination, binaryName), O_RDWR|O_CREATE|O_TRUNC, info.Mode()) and this error wraps that call's failure. The file could not be created or opened for writing at the destination path, using the source binary's permission bits.

Source

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

	}

	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
}

func (b Builder) getBinaryName() string {
	name := b.cfg.Name
	if runtime.GOOS == "windows" {
		name += ".exe"
	}

View on GitHub (pinned to ed7a235d2d)

Solutions

  1. Ensure cfg.Destination is set to an existing writable directory before Build
  2. Check write permission on the destination directory (ls -ld); chmod or choose another directory
  3. Check whether a directory already exists at filepath.Join(destination, binaryName) and remove/rename it
  4. If Destination is intentionally empty, run Build from the intended working directory or set Destination explicitly
  5. Inspect the wrapped errno: EACCES => permissions, EISDIR => target is a directory, ENOENT => bad destination path

Example fix

// before
Builder{cfg: Config{Destination: ""}}
// after
Builder{cfg: Config{Destination: "/tmp/release"}} // created via os.MkdirAll in copyBinary
Defensive patterns

Strategy: validation

Validate before calling

dest := cfg.Destination
if dest == "" { dest = "." }
if fi, err := os.Stat(dest); err != nil || !fi.IsDir() {
    return fmt.Errorf("destination %s is not an existing directory", dest)
}
target := filepath.Join(dest, binaryName)
if fi, err := os.Lstat(target); err == nil && fi.IsDir() {
    return fmt.Errorf("%s exists as a directory; remove it first", target)
}

Try / catch

if err := b.copyBinary(name); err != nil {
    var perr *fs.PathError
    switch {
    case errors.As(err, &perr) && errors.Is(perr, fs.ErrPermission):
        log.Fatalf("cannot write %s: permission denied (chmod the directory or change Destination)", perr.Path)
    default:
        return err
    }
}

Prevention

When it happens

Trigger: Destination directory exists (or was just created) but OpenFile fails: no write permission in the directory, the target name exists as a directory, destination path is wrong so filepath.Join lands in a nonexistent directory (only possible when Destination == "" resolving to a relative path), disk full is not typical at create time but EACCES/ENOTDIR/EISDIR are.

Common situations: Installing over an existing directory named like the binary, running as non-root against a system bin directory, Destination left empty so the join resolves relative to CWD which doesn't exist, or SELinux/AppArmor blocking writes.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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