Repository: skypilot-org/skypilot
Pull request #5877 (2025-06-05 00:59:24)

use default cookie file for websocket proxy
<!-- Describe the changes in this PR -->



<!-- Describe the tests ran -->
<!-- Unit tests (tests/test_*.py) are part of GitHub CI; below are tests that launch on the cloud. -->

Tested (run the relevant ones):

- [ ] Code formatting: install pre-commit (auto-check on commit) or `bash format.sh`
- [ ] Any manual or new tests for this PR (please specify below)
- [ ] All smoke tests: `/smoke-test` (CI) or `pytest tests/test_smoke.py` (local)
- [ ] Relevant individual tests: `/smoke-test -k test_name` (CI) or `pytest tests/test_smoke.py::test_name` (local)
- [ ] Backward compatibility: `/quicktest-core` (CI) or `pytest tests/smoke_tests/test_backward_compat.py` (local)

<!-- CI commands (/-prefixed) can only be triggered by repo members -->



Patch:
diff --git a/sky/server/constants.py b/sky/server/constants.py
index 3016e43fc..5a85dcd56 100644
--- a/sky/server/constants.py
+++ b/sky/server/constants.py
@@ -25,8 +25,10 @@ API_SERVER_REQUEST_DB_PATH = '~/.sky/api_server/requests.db'
 CLUSTER_REFRESH_DAEMON_INTERVAL_SECONDS = 60
 
 # Environment variable for a file path to the API cookie file.
+# Keep in sync with websocket_proxy.py
 API_COOKIE_FILE_ENV_VAR = f'{constants.SKYPILOT_ENV_VAR_PREFIX}API_COOKIE_FILE'
 # Default file if unset.
+# Keep in sync with websocket_proxy.py
 API_COOKIE_FILE_DEFAULT_LOCATION = '~/.sky/cookies.txt'
 
 # The path to the dashboard build output
diff --git a/sky/skypilot_config.py b/sky/skypilot_config.py
index fd7bba96f..4275d07e9 100644
--- a/sky/skypilot_config.py
+++ b/sky/skypilot_config.py
@@ -756,13 +756,15 @@ def apply_cli_config(cli_config: Optional[List[str]]) -> Dict[str, Any]:
     return parsed_config
 
 
-def update_config_no_lock(config: config_utils.Config) -> None:
+def update_api_server_config_no_lock(config: config_utils.Config) -> None:
     """Dumps the new config to a file and syncs to ConfigMap if in Kubernetes.
 
     Args:
         config: The config to save and sync.
     """
-    global_config_path = os.path.expanduser(get_user_config_path())
+    global_config_path = _resolve_server_config_path()
+    if global_config_path is None:
+        global_config_path = get_user_config_path()
 
     # Always save to the local file (PVC in Kubernetes, local file otherwise)
     common_utils.dump_yaml(global_config_path, dict(config))
diff --git a/sky/templates/websocket_proxy.py b/sky/templates/websocket_proxy.py
index 7847099ba..7726929a5 100644
--- a/sky/templates/websocket_proxy.py
+++ b/sky/templates/websocket_proxy.py
@@ -21,11 +21,21 @@ from websockets.asyncio.client import connect
 
 BUFFER_SIZE = 2**16  # 64KB
 
+# Environment variable for a file path to the API cookie file.
+# Keep in sync with server/constants.py
+API_COOKIE_FILE_ENV_VAR = 'SKYPILOT_API_COOKIE_FILE'
+# Default file if unset.
+# Keep in sync with server/constants.py
+API_COOKIE_FILE_DEFAULT_LOCATION = '~/.sky/cookies.txt'
+
 
 def _get_cookie_header(url: str) -> Dict[str, str]:
     """Extract Cookie header value from a cookie jar for a specific URL"""
-    cookie_path = os.environ.get('SKYPILOT_API_COOKIE_FILE')
+    cookie_path = os.environ.get(API_COOKIE_FILE_ENV_VAR)
     if cookie_path is None:
+        cookie_path = API_COOKIE_FILE_DEFAULT_LOCATION
+    cookie_path = os.path.expanduser(cookie_path)
+    if not os.path.exists(cookie_path):
         return {}
 
     request = Request(url)
diff --git a/sky/workspaces/core.py b/sky/workspaces/core.py
index da831154d..35ba912ea 100644
--- a/sky/workspaces/core.py
+++ b/sky/workspaces/core.py
@@ -66,7 +66,7 @@ def _update_workspaces_config(
             current_config['workspaces'] = current_workspaces
 
             # Write the configuration back to the file
-            skypilot_config.update_config_no_lock(current_config)
+            skypilot_config.update_api_server_config_no_lock(current_config)
 
             return current_workspaces
     except filelock.Timeout as e:
@@ -411,7 +411,7 @@ def update_config(config: Dict[str, Any]) -> Dict[str, Any]:
                                _WORKSPACE_CONFIG_LOCK_TIMEOUT_SECONDS):
             # Convert to config_utils.Config and save
             config_obj = config_utils.Config.from_dict(config)
-            skypilot_config.update_config_no_lock(config_obj)
+            skypilot_config.update_api_server_config_no_lock(config_obj)
     except filelock.Timeout as e:
         raise RuntimeError(
             f'Failed to update configuration due to a timeout '


Test patch:
diff --git a/tests/unit_tests/test_sky/utils/kubernetes/test_skypilot_config_configmap_sync.py b/tests/unit_tests/test_sky/utils/kubernetes/test_skypilot_config_configmap_sync.py
index 04670d126..1c2021588 100644
--- a/tests/unit_tests/test_sky/utils/kubernetes/test_skypilot_config_configmap_sync.py
+++ b/tests/unit_tests/test_sky/utils/kubernetes/test_skypilot_config_configmap_sync.py
@@ -147,9 +147,9 @@ class TestConfigMapSync(unittest.TestCase):
                 'patch_configmap_with_config')
     @mock.patch('sky.utils.kubernetes.config_map_utils.'
                 'is_running_in_kubernetes')
-    def test_update_config_no_lock_calls_patch_in_kubernetes(
+    def test_update_api_server_config_no_lock_calls_patch_in_kubernetes(
             self, mock_is_k8s, mock_patch):
-        """Test that update_config_no_lock calls ConfigMap patching in K8s."""
+        """Test that update_api_server_config_no_lock calls ConfigMap patching in K8s."""
         mock_is_k8s.return_value = True
 
         with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
@@ -162,7 +162,7 @@ class TestConfigMapSync(unittest.TestCase):
              mock.patch('sky.skypilot_config._reload_config'):
 
             config = config_utils.Config({'test': 'value'})
-            skypilot_config.update_config_no_lock(config)
+            skypilot_config.update_api_server_config_no_lock(config)
 
             # In Kubernetes, should call both dump_yaml and ConfigMap patching
             mock_patch.assert_called_once_with(config, config_path)
@@ -174,9 +174,9 @@ class TestConfigMapSync(unittest.TestCase):
                 'patch_configmap_with_config')
     @mock.patch('sky.utils.kubernetes.config_map_utils.'
                 'is_running_in_kubernetes')
-    def test_update_config_no_lock_calls_dump_yaml_non_kubernetes(
+    def test_update_api_server_config_no_lock_calls_dump_yaml_non_kubernetes(
             self, mock_is_k8s, mock_patch):
-        """Test that update_config_no_lock calls dump_yaml in non-K8s envs."""
+        """Test that update_api_server_config_no_lock calls dump_yaml in non-K8s envs."""
         mock_is_k8s.return_value = False
 
         with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
@@ -189,7 +189,7 @@ class TestConfigMapSync(unittest.TestCase):
              mock.patch('sky.skypilot_config._reload_config'):
 
             config = config_utils.Config({'test': 'value'})
-            skypilot_config.update_config_no_lock(config)
+            skypilot_config.update_api_server_config_no_lock(config)
 
             # In non-Kubernetes, should call dump_yaml but not ConfigMap patch
             mock_dump_yaml.assert_called_once_with(config_path, dict(config))