k3s-io/k3s · warning
this server has not yet been promoted from learner to…
Error message
this server has not yet been promoted from learner to voting member
What it means
ETCD.Test() rejects a datastore health check when the member's status reports IsLearner: the server was added via MemberAddAsLearner and has not yet been promoted to a voting member. Learners cannot serve consensus, so the server is not yet a full member; promotion happens automatically once the learner catches up.
Solutions
- Treat as transient: wait and retry; promotion is automatic once the learner's raft log catches up.
- If stuck, check network/latency to peers and etcd health (member list shows isLearner flag); verify the cluster has quorum to promote.
- As last resort remove the stuck learner (etcdctl member remove) and rejoin the node fresh.
Defensive patterns
Strategy: retry
Validate before calling
// Poll member status until promotion before declaring the node ready:
func waitNotLearner(ctx context.Context, cli *clientv3.Client, memberID uint64) error {
return wait.PollUntilContextCancel(ctx, 5*time.Second, true, func(ctx context.Context) (bool, error) {
ml, err := cli.MemberList(ctx)
if err != nil { return false, nil }
for _, m := range ml.Members {
if m.ID == memberID { return !m.IsLearner, nil }
}
return false, nil
})
} Try / catch
if err := etcd.Test(ctx, false); err != nil {
if strings.Contains(err.Error(), "not yet been promoted from learner") {
// transient by design: backoff and re-Test; only page a human after N minutes
}
} Prevention
- Treat learner status as expected during joins; size health-check grace periods to catch-up time.
- Monitor etcdctl endpoint status isLearner flag and alert only on learners older than a threshold.
- Keep cluster quorum healthy before adding members so promotion can proceed.
When it happens
Trigger: Immediately after adding a new server to an etcd cluster: the joiner sits as learner while syncing the datastore; Test() is called during startup before the catch-up completes; a stalled learner that never catches up never gets promoted.
Common situations: Normal (transient) during server joins, resolving in seconds-to-minutes; a chronically underprovisioned/slow network node stuck as learner; a learner added against a quorum-impaired cluster.
Related errors
- duplicate node name found, please use a unique name for…
- tombstone file has been detected but --server is empty…
- apiserver not ready
- bootstrap data already found and encrypted with different…
- cannot use S3 config secret when restoring snapshot…
AI-assisted analysis of k3s-io/k3s@6ba341e396 (2026-08-15).
Data as JSON: /api/errors/8674e9f98ca5d291.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/etcd/etcd.go:211
// Test ensures that the local node is a voting member of the target cluster
// and not in maintenance mode due to alarms.
// If it is still a learner or not a part of the cluster, an error is raised.
// If enableMaintenance is true, an attempt will be made to clear alarms.
// Startup defragmentation is delegated to etcd itself via the
// bootstrap-defrag-threshold-megabytes flag.
func (e *ETCD) Test(ctx context.Context, enableMaintenance bool) error {
if e.config == nil {
return errors.New("control config not set")
}
if e.client == nil {
return errors.New("etcd datastore is not started")
}
status, err := e.status(ctx)
if err != nil {
return errors.WithMessage(err, "failed to get etcd status")
} else if status.IsLearner {
return errors.New("this server has not yet been promoted from learner to voting member")
} else if status.Leader == 0 {
return errorsv3.ErrNoLeader
}
logrus.Infof("Connected to etcd v%s - datastore using %d of %d bytes", status.Version, status.DbSizeInUse, status.DbSize)
if len(status.Errors) > 0 {
logrus.Warnf("Errors present on etcd cluster: %s", strings.Join(status.Errors, ","))
}
if !enableMaintenance {
return nil
}
// clear alarms on this node
if err := e.clearAlarms(ctx, status.Header.MemberId); err != nil {
return errors.WithMessage(err, "failed to disarm etcd alarms")
}View on GitHub (pinned to 6ba341e396)