googleapis/mcp-toolbox · error

failed to write to file: %w

Error message

failed to write to file: %w

What it means

Thrown when `os.WriteFile(filePath, newBuf, info.Mode().Perm())` fails after the original file was already renamed to `.bak`. runMigrate then attempts an automatic restore: it removes the possibly partial file and renames the `.bak` back to the original path, joining any secondary errors into one message. The file's original permissions are preserved via the saved mode, so the failure is typically environmental (disk full, permissions, I/O error) rather than a code bug.

Source

Thrown at cmd/internal/migrate/command.go:113

				errMsg := fmt.Errorf("failed to stat file: %w", err)
				logger.ErrorContext(ctx, errMsg.Error())
				errs = append(errs, errMsg)
				continue
			}
			backupFile := filePath + ".bak"
			err = os.Rename(filePath, backupFile)
			if err != nil {
				errMsg := fmt.Errorf("failed to rename file: %w", err)
				logger.ErrorContext(ctx, errMsg.Error())
				errs = append(errs, errMsg)
				continue
			}
			logger.DebugContext(ctx, fmt.Sprintf("successfully renamed %s to %s", filePath, backupFile))

			// set the permission to the original file's permission.
			err = os.WriteFile(filePath, newBuf, info.Mode().Perm())
			if err != nil {
				errMsg := fmt.Errorf("failed to write to file: %w", err)
				// restoring original file
				if removeErr := os.Remove(filePath); removeErr != nil { // Attempt to remove the possibly partial file to ensure Rename succeeds.
					errMsg = errors.Join(errMsg, removeErr)
				}
				if restoreErr := os.Rename(backupFile, filePath); restoreErr != nil {
					fullRestoreErr := fmt.Errorf("failed to restore original file: %w", restoreErr)
					errMsg = errors.Join(errMsg, fullRestoreErr)
				}
				logger.ErrorContext(ctx, errMsg.Error())
				errs = append(errs, errMsg)
				continue
			}
			logger.DebugContext(ctx, fmt.Sprintf("migration completed for file: %s", filePath))
		}
	}

	logger.InfoContext(ctx, "migration ended!")
	// If errs is empty, errors.Join returns nil

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Free disk space or increase the volume/quota, then re-run migrate (the original file was restored from `.bak`, so it is safe to retry).
  2. Check for a leftover `.bak` next to the file; if the file content looks wrong, manually `mv file.bak file` to restore and investigate.
  3. Verify the effective user can write to the directory (`touch <dir>/.probe`) and adjust chown/chmod or run with elevated privileges.
  4. Check `dmesg`/journal for I/O errors on the backing device; move the file to healthy storage and retry.
  5. If SELinux is enforcing, check `audit.log` for denials and relabel with `restorecon`.

Example fix

// before: blind retry after disk-full
err = os.WriteFile(filePath, newBuf, info.Mode().Perm())
// after: check free space before rewriting
if st, serr := os.Statfs(dir); serr == nil && st.Bavail*uint64(st.Bsize) < uint64(len(newBuf)) {
    return fmt.Errorf("insufficient space in %s: need %d bytes", dir, len(newBuf))
}
err = os.WriteFile(filePath, newBuf, info.Mode().Perm())
Defensive patterns

Strategy: try-catch

Validate before calling

if st, err := syscall.Statfs(dir); err == nil && uint64(st.Bavail)*uint64(st.Bsize) < minBytesNeeded {
    return fmt.Errorf("not enough free space in %s", dir)
}

Try / catch

info, err := os.Stat(path)
if err != nil { return err }
if err := os.WriteFile(path, buf, info.Mode().Perm()); err != nil {
    // migrate already restores from .bak; verify the original is intact
    if _, statErr := os.Stat(path); statErr != nil {
        if bak, bErr := os.Stat(path + ".bak"); bErr == nil && bak.Size() > 0 {
            _ = os.Rename(path+".bak", path)
        }
    }
    return fmt.Errorf("write failed for %s: %w", path, err)
}

Prevention

When it happens

Trigger: Writing the migrated content fails because the disk is out of space, the user lacks write permission (though rename succeeded, the new file may inherit different context), an I/O error occurs (EIO), or a quota is exceeded. Also fires if the target file was recreated by a concurrent process with restrictive permissions.

Common situations: Migrating a large config on a nearly-full container overlay filesystem; running in CI where the workspace volume enforces quotas; SELinux/AppArmor denying write to the recreated file; NFS server hiccup causing EIO mid-write.

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 googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/580e91c60338803d. Report an issue: GitHub.