derailed/k9s · error

no valid selector found for job: %s

Error message

no valid selector found for job: %s

What it means

The Job log path converts the fetched Job and needs spec.selector.matchLabels to build the label selector that finds the Job's pods. A Job with a nil selector or zero matchLabels cannot be correlated to pods, so the DAO refuses to guess and errors, naming opts.Path.

Source

Thrown at internal/dao/job.go:89

	return ll, nil
}

// TailLogs tail logs for all pods represented by this Job.
func (j *Job) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) {
	o, err := j.getFactory().Get(j.gvr, opts.Path, true, labels.Everything())
	if err != nil {
		return nil, err
	}

	var job batchv1.Job
	err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &job)
	if err != nil {
		return nil, errors.New("expecting a job resource")
	}

	if job.Spec.Selector == nil || len(job.Spec.Selector.MatchLabels) == 0 {
		return nil, fmt.Errorf("no valid selector found for job: %s", opts.Path)
	}

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

func (j *Job) GetInstance(fqn string) (*batchv1.Job, error) {
	o, err := j.getFactory().Get(j.gvr, fqn, true, labels.Everything())
	if err != nil {
		return nil, err
	}

	var job batchv1.Job
	err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &job)
	if err != nil {
		return nil, errors.New("expecting a job resource")
	}

	return &job, nil

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Recreate the Job via kubectl/manifest so the API server generates the standard controller-uid selector
  2. Add an explicit spec.selector.matchLabels to the manifest matching spec.template.metadata.labels
  3. Workaround: fetch logs by selecting pods yourself with a known label or pod name instead of going through the Job

Example fix

# before (job.yaml, broken)
spec:
  template:
    spec:
      containers:
      - name: work
        image: busybox

# after
spec:
  selector:
    matchLabels:
      batch.kubernetes.io/controller-uid: work-uid
  template:
    metadata:
      labels:
        batch.kubernetes.io/controller-uid: work-uid
    spec:
      containers:
      - name: work
        image: busybox
Defensive patterns

Strategy: validation

Validate before calling

job, err := jobDAO.GetInstance(fqn)
if err != nil { return err }
if job.Spec.Selector == nil || len(job.Spec.Selector.MatchLabels) == 0 {
    return fmt.Errorf("job %s has no pod selector; cannot fetch logs via job", fqn)
}
return jobDAO.Logs(ctx, LogOptions{Path: fqn})

Type guard

func hasPodSelector(j *batchv1.Job) bool {
    return j.Spec.Selector != nil && len(j.Spec.Selector.MatchLabels) > 0
}

Try / catch

if err := jobDAO.Logs(ctx, opts); err != nil {
    if strings.Contains(err.Error(), "no valid selector found for job") {
        // fall back to selecting pods manually or by name
    }
}

Prevention

When it happens

Trigger: Requesting logs for a Job whose spec.selector is nil or whose matchLabels map is empty — hand-authored Job manifests missing the selector block, or Jobs created/mutated by controllers that strip selectors.

Common situations: Applying hand-written job.yaml without spec.selector (the API server normally injects batch.kubernetes.io/controller-uid, but direct writes or misbehaving admission webhooks can leave it empty); Jobs managed by custom operators that set their own ownership model.

Related errors


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