derailed/k9s · error

no valid selector found on deployment: %s

Error message

no valid selector found on deployment: %s

What it means

Deployment.TailLogs (internal/dao/dp.go:62-72) fans out log streaming to all pods a Deployment owns by listing pods via the Deployment's spec.selector.matchLabels. If that selector is nil or has no matchLabels, there is no way to find the pods, so it refuses with this error naming the deployment path.

Source

Thrown at internal/dao/dp.go:70

// Scale a Deployment.
func (d *Deployment) Scale(ctx context.Context, path string, replicas int32) error {
	return scaleRes(ctx, d.getFactory(), client.DpGVR, path, replicas)
}

// Restart a Deployment rollout.
func (d *Deployment) Restart(ctx context.Context, path string, opts *metav1.PatchOptions) error {
	return restartRes[*appsv1.Deployment](ctx, d.getFactory(), client.DpGVR, path, opts)
}

// TailLogs tail logs for all pods represented by this Deployment.
func (d *Deployment) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
	dp, err := d.GetInstance(opts.Path)
	if err != nil {
		return nil, err
	}
	if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 {
		return nil, fmt.Errorf("no valid selector found on deployment: %s", opts.Path)
	}

	return podLogs(ctx, dp.Spec.Selector.MatchLabels, opts)
}

// Pod returns a pod victim by name.
func (d *Deployment) Pod(fqn string) (string, error) {
	dp, err := d.GetInstance(fqn)
	if err != nil {
		return "", err
	}

	return podFromSelector(d.Factory, dp.Namespace, dp.Spec.Selector.MatchLabels)
}

// GetInstance fetch a matching deployment.
func (d *Deployment) GetInstance(fqn string) (*appsv1.Deployment, error) {
	o, err := d.Factory.Get(d.gvr, fqn, true, labels.Everything())

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Inspect the object: kubectl get deploy <name> -n <ns> -o jsonpath='{.spec.selector}'
  2. Fix the manifest/chart so spec.selector.matchLabels matches the pod template labels and re-apply
  3. As a workaround, tail logs from the pods view directly (pods are listed regardless of the owner's selector)
Defensive patterns

Strategy: validation

Validate before calling

// Guard before tailing: the deployment must expose a usable selector.
func HasSelector(dp *appsv1.Deployment) bool {
	return dp.Spec.Selector != nil && len(dp.Spec.Selector.MatchLabels) > 0
}

Type guard

func SelectableDeployment(dp *appsv1.Deployment) (map[string]string, error) {
    if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 {
        return nil, fmt.Errorf("no valid selector found on deployment: %s/%s", dp.Namespace, dp.Name)
    }
    return dp.Spec.Selector.MatchLabels, nil
}

Try / catch

dp, err := d.GetInstance(path)
if err != nil { return nil, err }
if sel, serr := SelectableDeployment(dp); serr == nil {
    return podLogs(ctx, sel, opts)
} else {
    // fallback: tail by the pod template's own labels instead
    return podLogs(ctx, dp.Spec.Template.Labels, opts)
}

Prevention

When it happens

Trigger: Calling log-tailing on a Deployment whose spec.selector is missing or whose matchLabels map is empty. This is normally rejected by the API server at create time, so it typically appears for objects created through unusual paths (faulty Helm charts, older API versions, direct etcd manipulation) or already-invalid cached objects.

Common situations: Charts that template an empty selector under conditional values; admissions webhooks stripping selectors; resources created before a cluster upgrade tightened validation.

Related errors


AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15). Data as JSON: /api/errors/28a62950ecef8e1b. Report an issue: GitHub.