helm/helm · error

unknown wait strategy (s%s). Valid values are: watcher, hook

Error message

unknown wait strategy (s%s). Valid values are: watcher, hookOnly, legacy

What it means

Returned by Client.GetWaiterWithOptions (pkg/kube/client.go:209) when the strategy string matches none of the three known values: watcher, hookOnly, legacy. Note the message is built by concatenation and contains a stray literal 's' — it renders as "unknown wait strategy (s<value>). Valid values are: ..." — a cosmetic quirk, but the valid-value list it prints is authoritative.

Source

Thrown at pkg/kube/client.go:209

	switch strategy {
	case LegacyStrategy:
		kc, err := c.Factory.KubernetesClientSet()
		if err != nil {
			return nil, err
		}
		return &legacyWaiter{kubeClient: kc, ctx: c.WaitContext}, nil
	case StatusWatcherStrategy:
		return c.newStatusWatcher(opts...)
	case HookOnlyStrategy:
		sw, err := c.newStatusWatcher(opts...)
		if err != nil {
			return nil, err
		}
		return &hookOnlyWaiter{sw: sw}, nil
	case "":
		return nil, errors.New("wait strategy not set. Choose one of: " + string(StatusWatcherStrategy) + ", " + string(HookOnlyStrategy) + ", " + string(LegacyStrategy))
	default:
		return nil, errors.New("unknown wait strategy (s" + string(strategy) + "). Valid values are: " + string(StatusWatcherStrategy) + ", " + string(HookOnlyStrategy) + ", " + string(LegacyStrategy))
	}
}

func (c *Client) SetWaiter(ws WaitStrategy) error {
	return c.SetWaiterWithOptions(ws)
}

func (c *Client) SetWaiterWithOptions(ws WaitStrategy, opts ...WaitOption) error {
	var err error
	c.Waiter, err = c.GetWaiterWithOptions(ws, opts...)
	if err != nil {
		return err
	}
	return nil
}

// New creates a new Client.
func New(getter genericclioptions.RESTClientGetter) *Client {

View on GitHub (pinned to 2a29f1770b)

Solutions

  1. Use the exported constants kube.StatusWatcherStrategy / kube.HookOnlyStrategy / kube.LegacyStrategy instead of raw strings
  2. Trim and validate the string against the three allowed values before calling SetWaiter
  3. Fix typos/case: the matcher is exact (watcher, not Watcher or watch)

Example fix

// before
c.SetWaiter(kube.WaitStrategy(strings.TrimSpace(cfg.WaitStrategy)))

// after — validate then use constants
switch strings.TrimSpace(cfg.WaitStrategy) {
case string(kube.StatusWatcherStrategy), string(kube.HookOnlyStrategy), string(kube.LegacyStrategy):
    c.SetWaiter(kube.WaitStrategy(strings.TrimSpace(cfg.WaitStrategy)))
default:
    return fmt.Errorf("invalid wait strategy %q", cfg.WaitStrategy)
}
Defensive patterns

Strategy: validation

Validate before calling

var allowedStrategies = map[kube.WaitStrategy]bool{
    kube.StatusWatcherStrategy: true,
    kube.HookOnlyStrategy:      true,
    kube.LegacyStrategy:        true,
}
if !allowedStrategies[kube.WaitStrategy(cfg.WaitStrategy)] {
    return fmt.Errorf("invalid wait strategy %q (want watcher, hookOnly, or legacy)", cfg.WaitStrategy)
}

Type guard

func isValidWaitStrategy(s string) bool {
    switch strings.TrimSpace(s) {
    case "watcher", "hookOnly", "legacy":
        return true
    }
    return false
}

Try / catch

if err := c.SetWaiterWithOptions(ws); err != nil {
    if strings.Contains(err.Error(), "unknown wait strategy") {
        return fmt.Errorf("config: %q is not a Helm v4 wait strategy (watcher|hookOnly|legacy): %w", ws, err)
    }
    return err
}

Prevention

When it happens

Trigger: c.SetWaiter(kube.WaitStrategy("polling")), "watch", "Watcher", or any typo/case variant — anything not exactly watcher, hookOnly, or legacy hits the default branch.

Common situations: Migrating configuration from other tools or old Helm versions with different strategy names; case mismatches; trailing whitespace in config strings; hand-typed values instead of the exported constants.

Related errors


AI-assisted analysis of helm/helm@2a29f1770b (2026-08-15). Data as JSON: /api/errors/d3838764219dfc0a. Report an issue: GitHub.