derailed/k9s · warning

did not phase down correctly expecting 0 replicas but got %d

Error message

did not phase down correctly expecting 0 replicas but got %d

What it means

ReplicaSet.diagnose is the VALID-column health check. When rs.Status.Replicas equals 0 but rs.Status.ReadyReplicas is non-zero — the controller reports zero total replicas while some pods still count as ready — it returns 'did not phase down correctly expecting 0 replicas but got N'. The message signals a scale-down whose status counters have not converged (typically pods still terminating or a stale status cache). It is rendered as status text via AsStatus and never returned to the caller as a failure.

Source

Thrown at internal/render/rs.go:102

		rs.Name,
		computeVulScore(rs.Namespace, rs.Labels, &rs.Spec.Template.Spec),
		strconv.Itoa(int(*rs.Spec.Replicas)),
		strconv.Itoa(int(rs.Status.Replicas)),
		strconv.Itoa(int(rs.Status.ReadyReplicas)),
		strings.Join(cos, ","),
		strings.Join(imgs, ","),
		mapToStr(rs.Labels),
		AsStatus(r.diagnose(&rs)),
		ToAge(rs.GetCreationTimestamp()),
	}

	return nil
}

func (ReplicaSet) diagnose(rs *appsv1.ReplicaSet) error {
	if rs.Status.Replicas != rs.Status.ReadyReplicas {
		if rs.Status.Replicas == 0 {
			return fmt.Errorf("did not phase down correctly expecting 0 replicas but got %d", rs.Status.ReadyReplicas)
		}
		return fmt.Errorf("mismatch desired(%d) vs ready(%d)", rs.Status.Replicas, rs.Status.ReadyReplicas)
	}

	return nil
}

View on GitHub (pinned to 2d3ccc6ba2)

Solutions

  1. Wait for terminating pods to finish: kubectl get pods -l <selector> until none remain
  2. Check finalizers and terminationGracePeriodSeconds keeping pods terminating
  3. Refresh the view — a stale informer cache can show the mismatch briefly
  4. If it persists, kubectl describe rs <name> and inspect controller events

Example fix

# before: rs at 0 replicas, pods still terminating
kubectl scale rs/myrs --replicas=0
kubectl get pods -l app=myrs
# after: wait for the ready counters to converge
kubectl rollout status rs/myrs --timeout=60s
Defensive patterns

Strategy: validation

Validate before calling

# catch the condition before it renders
kubectl get rs -o json | jq '.items[] | select(.status.replicas==0 and .status.readyReplicas>0) | {name:.metadata.name, ready:.status.readyReplicas}'

Prevention

When it happens

Trigger: An RS scaled to 0 (or being deleted) while its pods are still terminating; reading the RS view right after kubectl scale rs/<n> --replicas=0; momentary informer staleness during heavy churn. Any row where status.replicas==0 && status.readyReplicas>0 trips it.

Common situations: Scaling to zero before removing a deployment; CI batches deleting workloads; autoscaler floors at 0; long terminationGracePeriodSeconds or finalizers keeping pods alive.

Related errors


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