From 5ba62d2832dc9bebd7706200c46d4639da6fafab Mon Sep 17 00:00:00 2001 From: chaosinthecrd Date: Thu, 12 Feb 2026 16:04:01 +0000 Subject: [PATCH] ipn,control: adds node not found to ipn.Notify Updates #18700 Signed-off-by: chaosinthecrd --- ipn/backend.go | 9 +++++++++ ipn/ipnlocal/local.go | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/ipn/backend.go b/ipn/backend.go index 3183c8b5e..e647b7c92 100644 --- a/ipn/backend.go +++ b/ipn/backend.go @@ -157,6 +157,12 @@ type Notify struct { // any changes to the user in the UI. Health *health.State `json:",omitzero"` + // NodeRemoved, if non-nil, indicates that this node has been removed + // from the tailnet by the control plane. When set, the value is a + // message describing why the node was removed (e.g., "node not found", + // "node deleted by admin", etc.) + NodeRemoved *string `json:",omitzero"` + // SuggestedExitNode, if non-nil, is the node that the backend has determined to // be the best exit node for the current network conditions. SuggestedExitNode *tailcfg.StableNodeID `json:",omitzero"` @@ -203,6 +209,9 @@ func (n Notify) String() string { if n.SuggestedExitNode != nil { fmt.Fprintf(&sb, "SuggestedExitNode=%v ", *n.SuggestedExitNode) } + if n.NodeRemoved != nil { + fmt.Fprintf(&sb, "nodeRemoved=%q ", *n.NodeRemoved) + } s := sb.String() if s == "Notify{" { diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 27858484a..b5b5fda57 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -1583,6 +1583,11 @@ func (b *LocalBackend) SetControlClientStatus(c controlclient.Client, st control return } b.logf("Received error: %v", st.Err) + errMsg := st.Err.Error() + if strings.Contains(errMsg, "404") && strings.Contains(errMsg, "node not found") { + reason := "This node has been removed from the tailnet" + b.sendLocked(ipn.Notify{NodeRemoved: &reason}) + } var uerr controlclient.UserVisibleError if errors.As(st.Err, &uerr) { s := uerr.UserVisibleError()