golangci/golangci-lint · error

saving configuration file: %w

Error message

saving configuration file: %w

What it means

This error wraps any failure that occurs while writing the migrated (v2) configuration file to disk during `golangci-lint migrate`. The command converts a v1 config into the new format via `saveNewConfiguration`; if that helper fails (file creation, marshaling, or write error), it is wrapped with this message and returned to the caller. It is a shell error — the root cause is always the wrapped inner error.

Source

Thrown at pkg/commands/migrate.go:123

	}

	if !strings.EqualFold(filepath.Ext(srcPath), ext) {
		defer func() {
			_ = os.RemoveAll(srcPath)
		}()
	}

	if c.cfg.Run.Timeout != 0 {
		c.log.Warnf("The configuration `run.timeout` is ignored. By default, in v2, the timeout is disabled.")
	}

	newCfg := migrate.ToConfig(c.cfg)

	dstPath := strings.TrimSuffix(srcPath, filepath.Ext(srcPath)) + ext

	err = saveNewConfiguration(newCfg, dstPath)
	if err != nil {
		return fmt.Errorf("saving configuration file: %w", err)
	}

	c.log.Infof("Migration done: %s", dstPath)

	callForAction(c.cmd)

	return nil
}

func (c *migrateCommand) preRunE(cmd *cobra.Command, _ []string) error {
	switch strings.ToLower(c.opts.format) {
	case "", "yml", "yaml", "toml", "json":
		// Valid format.
	default:
		return fmt.Errorf("unsupported format: %s", c.opts.format)
	}

	if c.cfg.Version != "" {

View on GitHub (pinned to ed7a235d2d)

Solutions

  1. Read the wrapped error after this message to find the root cause (e.g. 'permission denied', 'no such file or directory').
  2. Ensure the migration output directory exists and is writable by the current user (chmod/chown or run from a writable copy).
  3. Copy the project (or just the config) to a writable location and run migrate there.
  4. If the file is locked, close the program holding it, then rerun migrate.
  5. Check disk space with `df -h` if the error indicates a full filesystem.

Example fix

// before
golangci-lint migrate   # run in a read-only CI checkout
// after
chmod u+w .golangci.yml && golangci-lint migrate
# or run in a writable copy of the repo
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: verify the migration destination is writable before running migrate
dst := strings.TrimSuffix(src, filepath.Ext(src)) + ".yml"
if f, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, 0o644); err != nil {
    return fmt.Errorf("destination %s not writable: %w", dst, err)
} else {
    f.Close()
}

Try / catch

if err := saveNewConfiguration(newCfg, dstPath); err != nil {
    var pe *fs.PathError
    if errors.As(err, &pe) {
        log.Printf("write failed at %s: %v (op=%s)", pe.Path, pe.Err, pe.Op)
    }
    return fmt.Errorf("saving configuration file: %w", err)
}

Prevention

When it happens

Trigger: Running `golangci-lint migrate` when the destination path (source path with extension replaced) is not writable, e.g. the directory does not exist, permissions deny writing, the target file is locked/read-only, or serializing the new config to the requested format (yml/toml/json) fails.

Common situations: Running migrate in a read-only checkout or CI workspace; migrating a config in a directory the user cannot write to; a read-only mounted volume; the output file being open in an editor with a lock; an unsupported/invalid format value that passes validation but fails serialization.

Understand the failure class

Background: "Permission denied" / "Failed to write" file errors: why a library can't write its files to disk (EACCES, EPERM, ENOSPC) and how to fix them — this error's family across 43 libraries.

Related errors


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