vitessio/vitess · error

preflight_mysqld_shutdown hook failed: %v

Error message

preflight_mysqld_shutdown hook failed: %v

What it means

Before shutting down mysqld, Vitess runs an optional user-supplied 'preflight_mysqld_shutdown' hook. If the hook exits with any status other than HOOK_SUCCESS or HOOK_DOES_NOT_EXIST, Shutdown aborts before stopping mysqld and returns this error containing the hook's result string. It is a deliberate extension point failure — the operator's pre-shutdown validation refused the shutdown.

Source

Thrown at go/vt/mysqlctl/mysqld.go:801

		}
		defer releaseFlock()
	}

	// possibly mysql is already shutdown, check for a few files first
	if mysqldAlreadyStopped(cnf) {
		log.Warn("assuming mysqld already shut down - no socket, no pid file found")
		return nil
	}

	// try the preflight mysqld shutdown hook, if any
	h := hook.NewSimpleHook("preflight_mysqld_shutdown")
	hr := h.ExecuteContext(ctx)
	switch hr.ExitStatus {
	case hook.HOOK_SUCCESS, hook.HOOK_DOES_NOT_EXIST:
		// hook exists and worked, or else does not exist.
	default:
		// hook failed, we report error
		return fmt.Errorf("preflight_mysqld_shutdown hook failed: %v", hr.String())
	}

	return mysqld.shutdownWithReplicaCrashSafety(ctx, preparationBudget, func() (bool, error) {
		return mysqld.executeShutdown(ctx, cnf, waitForMysqld, shutdownTimeout)
	})
}

// replicaShutdownPreparationBudget returns how long the replica crash-safety
// preparation may take for the given shutdown timeout. A zero (or negative)
// shutdown timeout means the caller wants an immediate, no-wait shutdown
// (mysqladmin --shutdown-timeout=0): honor that by granting the preparation
// no budget at all rather than its default one.
func replicaShutdownPreparationBudget(shutdownTimeout time.Duration) time.Duration {
	if shutdownTimeout <= 0 {
		return 0
	}
	return min(replicaShutdownPreparationTimeout, shutdownTimeout)
}

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Inspect hr in the error message for the hook's exit status, stdout and stderr to see why the preflight script failed.
  2. Fix the condition the preflight hook checks (e.g. wait for replica lag to drain, take a backup) and retry the shutdown.
  3. Correct or disable the hook script if it is malfunctioning (bad shebang, wrong exit code conventions).
  4. Remember hooks returning HOOK_DOES_NOT_EXIST are fine — only a real non-success exit blocks shutdown.

Example fix

// before
#!/bin/sh
exit 1  # debugging leftover
// after
#!/bin/sh
# verify replica is caught up
mysql -e 'SELECT 1' || exit 1
exit 0
Defensive patterns

Strategy: validation

Validate before calling

hr := hook.NewHook("preflight_mysqld_shutdown").ExecuteContext(ctx)
if hr.ExitStatus != hook.HOOK_SUCCESS && hr.ExitStatus != hook.HOOK_DOES_NOT_EXIST {
    log.Infof("preflight would fail: %s", hr.String())
}

Type guard

func hookSucceeded(hr *hook.HookResult) bool {
    return hr.ExitStatus == hook.HOOK_SUCCESS || hr.ExitStatus == hook.HOOK_DOES_NOT_EXIST
}

Try / catch

if err := mysqld.Shutdown(ctx, cnf, true, timeout); err != nil && strings.Contains(err.Error(), "preflight_mysqld_shutdown hook failed") {
    // read hr details from message, resolve the blocking condition, retry
}

Prevention

When it happens

Trigger: Mysqld.Shutdown executes the preflight_mysqld_shutdown hook via hook.ExecuteContext; the hook script exits non-zero (not 0/SUCCESS and not DOES_NOT_EXIST), e.g. exit status 1 from a custom guard script.

Common situations: A site-specific preflight script checks replication lag or backup state and fails on a laggy replica; the hook script itself has a bug or wrong interpreter path; the hook returns an unexpected status code such as HOOK_PARTIAL_SUCCESS being treated as failure.

Related errors


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