lima-vm/lima · error

failed to write to sudoers file %#q for user %#q

Error message

failed to write to sudoers file %#q for user %#q: %w

What it means

The fmt.Fprintf write of the `<userName> <sudo>` line to /etc/sudoers.d/90-cloud-init-users failed after the file was opened successfully. The file handle is closed before returning, and provisioning of this user's sudo access aborts.

Solutions

  1. Free disk space in the guest if the wrapped error is ENOSPC
  2. Ensure only one provisioning pass runs at a time (avoid concurrent processUserData)
  3. Check volume health / I/O errors in the wrapped cause
  4. Reprovision the user after fixing; the file is opened with O_APPEND so retries are safe
Defensive patterns

Strategy: retry

Validate before calling

if fi, err := os.Stat("/etc/sudoers.d/90-cloud-init-users"); err == nil && fi.Size() > 1<<20 {
    return errors.New("sudoers drop-in suspiciously large; inspect before appending")
}
// also ensure free space: syscall.Statfs("/etc", &st); st.Bavail*uint64(st.Bsize) > 1MB

Try / catch

if err := processUserData(ctx, data); err != nil {
    if strings.Contains(err.Error(), "write to sudoers file") {
        log.Printf("sudoers write failed: %v", err)
        // O_APPEND makes a retry after freeing space safe
        if retryErr := processUserData(ctx, data); retryErr != nil {
            log.Printf("retry failed: %v", retryErr)
        }
    }
}

Prevention

When it happens

Trigger: The write syscall behind fmt.Fprintf errors: ENOSPC (disk full), EBADF/EIO (I/O error on the file handle or underlying volume), or the file was concurrently removed/locked by another process between open and write.

Common situations: Disk-full VM during provisioning; concurrent provisioning processes appending to the same sudoers file; underlying APFS volume I/O errors.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of lima-vm/lima@dd909d0973 (2026-09-01). Data as JSON: /api/errors/b78c25d319b2c561. Report an issue: GitHub.

Appendix: source

Thrown at pkg/guestagent/fakecloudinit/fakecloudinit_darwin.go:334

}

// writeSudoers appends a sudoers entry for the given user.
// writeSudoers is expected be called only once on creating the user account.
func writeSudoers(userName, sudo string) error {
	if strings.Contains(sudo, "\n") {
		return errors.New("sudo field must not contain newline characters")
	}
	if err := os.MkdirAll("/etc/sudoers.d", 0o700); err != nil {
		return fmt.Errorf("failed to create /etc/sudoers.d directory: %w", err)
	}
	sudoersPath := "/etc/sudoers.d/90-cloud-init-users"
	f, err := os.OpenFile(sudoersPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o400)
	if err != nil {
		return fmt.Errorf("failed to open sudoers file %#q: %w", sudoersPath, err)
	}
	if _, err = fmt.Fprintf(f, "%s %s\n", userName, sudo); err != nil {
		_ = f.Close()
		return fmt.Errorf("failed to write to sudoers file %#q for user %#q: %w", sudoersPath, userName, err)
	}
	return f.Close()
}

func writeFiles(ctx context.Context, entry cloudinittypes.WriteFile) error {
	if entry.Path == "" {
		return errors.New("path is required for write_files entry")
	}
	perm := os.FileMode(0o644)
	if entry.Permissions != "" {
		p, err := strconv.ParseUint(entry.Permissions, 8, 32)
		if err != nil {
			return fmt.Errorf("invalid permissions %#q for path %#q: %w", entry.Permissions, entry.Path, err)
		}
		perm = os.FileMode(p)
	}
	if err := os.MkdirAll(filepath.Dir(entry.Path), 0o755); err != nil {
		return fmt.Errorf("failed to create parent directory for path %#q: %w", entry.Path, err)

View on GitHub (pinned to dd909d0973)