vitessio/vitess · error · ErrMySQLShellPreCheck

%w: unable to parse JSON of load flags

Error message

%w: unable to parse JSON of load flags

What it means

The mysql-shell load flags (--mysql-shell-load-flags) must be valid JSON because restorePreCheck() unmarshals them into a map. If json.Unmarshal fails, this error wrapping ErrMySQLShellPreCheck is returned and the restore is aborted.

Source

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

	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" {
		return false, fmt.Errorf("%w: mysql-shell needs to restore with updateGtidSet set to \"replace\" to work with Vitess", ErrMySQLShellPreCheck)
	}

	if val, ok := loadFlags["progressFile"]; !ok || val != "" {
		return false, fmt.Errorf("%w: \"progressFile\" needs to be empty as vitess always starts a restore from scratch", ErrMySQLShellPreCheck)
	}

	if val, ok := loadFlags["skipBinlog"]; !ok || val != true {
		return false, fmt.Errorf("%w: \"skipBinlog\" needs to set to true", ErrMySQLShellPreCheck)
	}

	if val, ok := loadFlags["loadUsers"]; ok && val == true {
		shouldDeleteUsers = true
	}

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Validate the flag value with `jq . <<< '<value>'` or json.Unmarshal in a scratch program; fix the JSON syntax.
  2. Escape double quotes properly for the launch layer (systemd Environment=, Helm values, etc.).
  3. Use a minimal correct value first: '{"updateGtidSet":"replace","progressFile":""}' and add keys incrementally.

Example fix

// before: invalid JSON (single quotes)
--mysql-shell-load-flags "{'updateGtidSet':'replace'}"
// after
--mysql-shell-load-flags "{\"updateGtidSet\":\"replace\",\"progressFile\":\"\"}"
Defensive patterns

Strategy: validation

Validate before calling

probe := map[string]any{}
if err := json.Unmarshal([]byte(mysqlShellLoadFlags), &probe); err != nil {
	return fmt.Errorf("--mysql-shell-load-flags is not valid JSON: %v", err)
}

Try / catch

err := engine.ExecuteRestore(ctx, restoreParams)
if errors.Is(err, mysqlctl.ErrMySQLShellLoadFlagsNotParseable) || strings.Contains(err.Error(), "unable to parse JSON of load flags") {
	// fix JSON quoting in the launch layer, then rerun
	return err
}

Prevention

When it happens

Trigger: ExecuteRestore -> restorePreCheck when --mysql-shell-load-flags contains malformed JSON: single quotes instead of double, trailing commas, unquoted keys, or shell-mangled quoting stripping double quotes.

Common situations: Flags passed through systemd units, Helm charts, or shell scripts where double quotes need escaping and get consumed by an intermediate shell layer; hand-edited config introducing a trailing comma.

Understand the failure class

Related errors


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