{"record":{"id":"b25daaa5f5e74c1a","repo":"tailscale/tailscale","slug":"exit-node-cannot-be-changed-w","errorCode":null,"errorMessage":"exit node cannot be changed: %w","messagePattern":"exit node cannot be changed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ipn/ipnlocal/local.go","lineNumber":5224,"sourceCode":"\n\t// Prevent users from changing exit node preferences\n\t// when exit node usage is managed by policy.\n\tif mp.ExitNodeIDSet || mp.ExitNodeIPSet || mp.AutoExitNodeSet {\n\t\tisManaged, err := b.polc.HasAnyOf(pkey.ExitNodeID, pkey.ExitNodeIP)\n\t\tif err != nil {\n\t\t\terr = fmt.Errorf(\"policy check failed: %w\", err)\n\t\t} else if isManaged {\n\t\t\t// Allow users to override ExitNode policy settings and select an exit node manually\n\t\t\t// if permitted by [pkey.AllowExitNodeOverride].\n\t\t\t//\n\t\t\t// Disabling exit node usage entirely is not allowed.\n\t\t\tallowExitNodeOverride, _ := b.polc.GetBoolean(pkey.AllowExitNodeOverride, false)\n\t\t\tif !allowExitNodeOverride || b.changeDisablesExitNodeLocked(prefs, mp) {\n\t\t\t\terr = errManagedByPolicy\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\terrs = append(errs, fmt.Errorf(\"exit node cannot be changed: %w\", err))\n\t\t}\n\t}\n\n\treturn errors.Join(errs...)\n}\n\n// changeDisablesExitNodeLocked reports whether applying the change\n// to the given prefs would disable exit node usage.\n//\n// In other words, it returns true if prefs.ExitNodeID is non-empty\n// initially, but would become empty after applying the given change.\n//\n// It applies the same adjustments and resolves the exit node in the prefs\n// as done during actual edits. While not optimal performance-wise,\n// changing the exit node via LocalAPI isn't a hot path, and reusing\n// the same logic ensures consistency and simplifies maintenance.\n//\n// b.mu must be held.","sourceCodeStart":5206,"sourceCodeEnd":5242,"githubUrl":"https://github.com/tailscale/tailscale/blob/5201273aec737d6372ab7423c31c04ca3ca2a0c2/ipn/ipnlocal/local.go#L5206-L5242","documentation":"checkPrefs: the user tried to change exit node prefs (ExitNodeID/ExitNodeIP/AutoExitNode) that are managed by syspolicy, and either the AllowExitNodeOverride policy did not permit the override or the user attempted to disable exit node usage entirely. A policy-enforced rejection.","triggerScenarios":"Thrown at ipn/ipnlocal/local.go:5218 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Disconnect or adjust the conflicting setting before changing the exit node."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"5201273aec737d6372ab7423c31c04ca3ca2a0c2","analyzedAt":"2026-08-18T08:17:25.280Z","contentChangedAt":"2026-08-18T08:17:25.280Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}