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

  1. Refresh the node and skip the call when node.Spec.Unschedulable is already false
  2. Treat the error as success in idempotent workflows (log-and-continue)
  3. 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

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


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