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
- Inspect the object: kubectl get deploy <name> -n <ns> -o jsonpath='{.spec.selector}'
- Fix the manifest/chart so spec.selector.matchLabels matches the pod template labels and re-apply
- 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
- Lint deployments in CI: spec.selector.matchLabels must be non-empty and match template labels
- Never apply workloads with --validate=false
- Audit clusters periodically for selector-less workloads (kyverno/polaris policies)
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
- no valid selector found on daemonset %q
- user is not authorized to patch a deployment
- no matching pods for %v
- expecting Deployment resource
- can not rollback current replica
AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15).
Data as JSON: /api/errors/28a62950ecef8e1b.
Report an issue: GitHub.