{"record":{"id":"8674e9f98ca5d291","repo":"k3s-io/k3s","slug":"this-server-has-not-yet-been-promoted-from-learner","errorCode":null,"errorMessage":"this server has not yet been promoted from learner to voting member","messagePattern":"this server has not yet been promoted from learner to voting member","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/etcd/etcd.go","lineNumber":211,"sourceCode":"// Test ensures that the local node is a voting member of the target cluster\n// and not in maintenance mode due to alarms.\n// If it is still a learner or not a part of the cluster, an error is raised.\n// If enableMaintenance is true, an attempt will be made to clear alarms.\n// Startup defragmentation is delegated to etcd itself via the\n// bootstrap-defrag-threshold-megabytes flag.\nfunc (e *ETCD) Test(ctx context.Context, enableMaintenance bool) error {\n\tif e.config == nil {\n\t\treturn errors.New(\"control config not set\")\n\t}\n\tif e.client == nil {\n\t\treturn errors.New(\"etcd datastore is not started\")\n\t}\n\n\tstatus, err := e.status(ctx)\n\tif err != nil {\n\t\treturn errors.WithMessage(err, \"failed to get etcd status\")\n\t} else if status.IsLearner {\n\t\treturn errors.New(\"this server has not yet been promoted from learner to voting member\")\n\t} else if status.Leader == 0 {\n\t\treturn errorsv3.ErrNoLeader\n\t}\n\n\tlogrus.Infof(\"Connected to etcd v%s - datastore using %d of %d bytes\", status.Version, status.DbSizeInUse, status.DbSize)\n\n\tif len(status.Errors) > 0 {\n\t\tlogrus.Warnf(\"Errors present on etcd cluster: %s\", strings.Join(status.Errors, \",\"))\n\t}\n\n\tif !enableMaintenance {\n\t\treturn nil\n\t}\n\n\t// clear alarms on this node\n\tif err := e.clearAlarms(ctx, status.Header.MemberId); err != nil {\n\t\treturn errors.WithMessage(err, \"failed to disarm etcd alarms\")\n\t}","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/k3s-io/k3s/blob/6ba341e396edc16b8dcae978a7c5e3ac7ee5606e/pkg/etcd/etcd.go#L193-L229","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Poll member status until promotion before declaring the node ready:\nfunc waitNotLearner(ctx context.Context, cli *clientv3.Client, memberID uint64) error {\n\treturn wait.PollUntilContextCancel(ctx, 5*time.Second, true, func(ctx context.Context) (bool, error) {\n\t\tml, err := cli.MemberList(ctx)\n\t\tif err != nil { return false, nil }\n\t\tfor _, m := range ml.Members {\n\t\t\tif m.ID == memberID { return !m.IsLearner, nil }\n\t\t}\n\t\treturn false, nil\n\t})\n}","typeGuard":null,"tryCatchPattern":"if err := etcd.Test(ctx, false); err != nil {\n\tif strings.Contains(err.Error(), \"not yet been promoted from learner\") {\n\t\t// transient by design: backoff and re-Test; only page a human after N minutes\n\t}\n}","preventionTips":["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."],"tags":["k3s","etcd","learner","raft","join","transient"],"backgroundTag":null,"analyzedSha":"6ba341e396edc16b8dcae978a7c5e3ac7ee5606e","analyzedAt":"2026-08-15T16:27:54.286Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}