lima-vm/lima · error
failed to write to sudoers file %#q for user %#q: %w
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.
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)
Solutions
- Free disk space in the guest if the wrapped error is ENOSPC
- Ensure only one provisioning pass runs at a time (avoid concurrent processUserData)
- Check volume health / I/O errors in the wrapped cause
- 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
- Keep guest disks sized with headroom
- Serialize provisioning runs (no concurrent processUserData)
- Monitor ENOSPC/EIO in guestagent logs and alert early
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
- failed to write password file for user %#q: %w
- failed to create .ssh directory for user %#q: %w
- failed to write authorized_keys file for user %#q: %w
- failed to write sudoers file for user %#q: %w
- failed to create /etc/sudoers.d directory: %w
AI-assisted analysis of lima-vm/lima@dd909d0973 (2026-09-01).
Data as JSON: /api/errors/b78c25d319b2c561.
Report an issue: GitHub.