vitessio/vitess · error

failure creating directory %s: %w

Error message

failure creating directory %s: %w

What it means

When the dump target is local filesystem (not object store flags), backupPreCheck() creates the target directory with os.MkdirAll(location, 0o750) because MySQL Shell does not create full paths. This error wraps the OS failure (permissions, read-only FS, bad path) when directory creation fails.

Source

Thrown at go/vt/mysqlctl/mysqlshellbackupengine.go:492

	}
	return fileutil.SafePathJoin(mysqlShellBackupLocation, dir, name)
}

func (be *MySQLShellBackupEngine) backupPreCheck(location string) error {
	if mysqlShellBackupLocation == "" {
		return fmt.Errorf("%w: no backup location set via --mysql-shell-backup-location", ErrMySQLShellPreCheck)
	}

	if mysqlShellFlags == "" || !strings.Contains(mysqlShellFlags, "--js") {
		return fmt.Errorf("%w: at least the --js flag is required in the value of the flag --mysql-shell-flags", ErrMySQLShellPreCheck)
	}

	// make sure the target directory exists if the target location for the backup is not an object store
	// (e.g. is the local filesystem) as MySQL Shell doesn't create the entire path beforehand:
	if !isObjectStoreFlags(mysqlShellDumpFlags) {
		err := os.MkdirAll(location, 0o750)
		if err != nil {
			return fmt.Errorf("failure creating directory %s: %w", location, err)
		}
	}

	return nil
}

func (be *MySQLShellBackupEngine) restorePreCheck(ctx context.Context, params RestoreParams) (shouldDeleteUsers bool, err error) {
	if mysqlShellFlags == "" {
		return shouldDeleteUsers, fmt.Errorf("%w: at least the --js flag is required in the value of the flag --mysql-shell-flags", ErrMySQLShellPreCheck)
	}

	loadFlags := map[string]any{}
	err = json.Unmarshal([]byte(mysqlShellLoadFlags), &loadFlags)
	if err != nil {
		return false, fmt.Errorf("%w: unable to parse JSON of load flags", ErrMySQLShellPreCheck)
	}

	if val, ok := loadFlags["updateGtidSet"]; !ok || val != "replace" {

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Check the exact wrapped OS error (permission denied vs read-only vs not a directory) and fix the filesystem accordingly.
  2. Pre-create the backup location with correct ownership for the tablet process user and mode 0750.
  3. Verify the volume is mounted inside the container/pod.
  4. If backing up to object storage, ensure the dump flags are recognized by isObjectStoreFlags so the mkdir is skipped.

Example fix

// before: mkdir fails because parent is unwritable
// failure creating directory /vt/backups/shard-0: mkdir /vt/backups: permission denied
// after: provision directory before starting tablet
sudo mkdir -p /vt/backups/shard-0
sudo chown vitess:vitess /vt/backups/shard-0
sudo chmod 0750 /vt/backups/shard-0
Defensive patterns

Strategy: validation

Validate before calling

if !isObjectStoreFlags(mysqlShellDumpFlags) {
	if err := os.MkdirAll(location, 0o750); err != nil {
		return fmt.Errorf("backup location %s unusable: %v", location, err)
	}
	if fi, err := os.Stat(location); err != nil || !fi.IsDir() {
		return fmt.Errorf("backup location %s is not a directory", location)
	}
}

Try / catch

err := engine.ExecuteBackup(ctx, backupParams)
if err != nil && strings.Contains(err.Error(), "failure creating directory") {
	// check mount, ownership, and SELinux before retrying
	return err
}

Prevention

When it happens

Trigger: ExecuteBackup -> backupPreCheck with a non-object-store location string that cannot be created: permission denied, nonexistent/unmountable parent, path is actually a file, or read-only filesystem.

Common situations: Backup location on a volume not mounted in the container; directory owned by another user (mysqld runs as 'mysql' or 'vitess'); SELinux denial; location string typo colliding with an existing file.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/59600e30196bbf17. Report an issue: GitHub.