k3s-io/k3s · warning
this server has not yet been promoted from learner to voting
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.
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)
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 this
- tombstone file has been detected but --server is empty: back
- no bootstrap data found in datastore - check server token va
- no bootstrap data is available to reconcile against
- critical configuration value mismatch between servers
AI-assisted analysis of k3s-io/k3s@6ba341e396 (2026-08-15).
Data as JSON: /api/errors/8674e9f98ca5d291.
Report an issue: GitHub.