derailed/k9s · warning
node is already uncordoned
Error message
node is already uncordoned
What it means
The uncordon mirror of the cordon guard: ToggleCordon(ctx, fqn, false) reaches UpdateIfRequired, which reports no change because spec.unschedulable is already false. The DAO returns this error instead of patching the node with an identical spec.
Source
Thrown at internal/dao/node.go:65
o, err := FetchNode(context.Background(), n.Factory, fqn)
if err != nil {
return err
}
h, err := drain.NewCordonHelperFromRuntimeObject(o, scheme.Scheme, n.gvr.GVK())
if err != nil {
slog.Debug("Fail to toggle cordon on node",
slogs.FQN, fqn,
slogs.Error, err,
)
return err
}
if !h.UpdateIfRequired(cordon) {
if cordon {
return fmt.Errorf("node is already cordoned")
}
return fmt.Errorf("node is already uncordoned")
}
dial, err := n.getFactory().Client().Dial()
if err != nil {
return err
}
err, patchErr := h.PatchOrReplace(dial, false)
if patchErr != nil {
return patchErr
}
if err != nil {
return err
}
return nil
}
func (o DrainOptions) toDrainHelper(k kubernetes.Interface, w io.Writer) drain.Helper {View on GitHub (pinned to 2d3ccc6ba2)
Solutions
- Refresh the node and skip the call when node.Spec.Unschedulable is already false
- Treat the error as success in idempotent workflows (log-and-continue)
- Check current state with kubectl cordon --dry-run equivalent: kubectl get node <n> -o jsonpath='{.spec.unschedulable}'
Example fix
// before
err := n.ToggleCordon(ctx, fqn, false)
// after
node, _ := dao.FetchNode(ctx, factory, fqn)
if node != nil && !node.Spec.Unschedulable {
return nil // already uncordoned
}
err := n.ToggleCordon(ctx, fqn, false) Defensive patterns
Strategy: validation
Validate before calling
node, err := dao.FetchNode(ctx, factory, fqn)
if err != nil { return err }
if !node.Spec.Unschedulable {
return nil // already uncordoned
}
return nodeDAO.ToggleCordon(ctx, fqn, false) Type guard
func isUncordoned(n *v1.Node) bool {
return n != nil && !n.Spec.Unschedulable
} Try / catch
if err := n.ToggleCordon(ctx, fqn, false); err != nil {
if strings.Contains(err.Error(), "already uncordoned") {
return nil // idempotent success
}
return err
} Prevention
- Unconditional uncordon scripts should swallow the 'already uncordoned' case as success
- Refresh node state right before toggling in concurrent environments (two operators)
- Verify final state with kubectl get node <n> -o jsonpath='{.spec.unschedulable}' after operations
When it happens
Trigger: Calling ToggleCordon(ctx, fqn, false) on a node that is not cordoned (spec.unschedulable == false), e.g. uncordon after a drain that already uncordoned.
Common situations: Post-drain cleanup scripts that uncordon unconditionally; UI toggles raced against another operator; running uncordon on a fresh node that was never cordoned.
Related errors
- node is already cordoned
- expecting a maintainer for %q
- node is cordoned
- unable to locate node %s
- user is not authorized to list nodes
AI-assisted analysis of derailed/k9s@2d3ccc6ba2 (2026-08-15).
Data as JSON: /api/errors/67c7343397d738e5.
Report an issue: GitHub.