Skip to content

Commit 128c2da

Browse files
authored
fix(cli): clear default_workspace on cloud logout (#773)
Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 2bfb9c7 commit 128c2da

2 files changed

Lines changed: 79 additions & 2 deletions

File tree

src/basic_memory/cli/commands/cloud/core_commands.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,22 @@ async def _login():
7676

7777
@cloud_app.command()
7878
def logout():
79-
"""Remove stored OAuth tokens."""
80-
config = ConfigManager().config
79+
"""Remove stored OAuth tokens and clear cached workspace selection."""
80+
config_manager = ConfigManager()
81+
config = config_manager.config
8182
auth = CLIAuth(client_id=config.cloud_client_id, authkit_domain=config.cloud_domain)
8283
auth.logout()
84+
85+
# Trigger: ending a session must invalidate the cached workspace.
86+
# Why: a follow-up `bm cloud login` (often as a different user, or returning
87+
# from an org workspace to personal) inherits the previous selection
88+
# and silently routes everything through the wrong tenant. See #755.
89+
# Outcome: re-login starts from a clean slate; the user picks again via
90+
# `bm cloud workspace set-default` or per-project --workspace.
91+
if config.default_workspace is not None:
92+
config.default_workspace = None
93+
config_manager.save_config(config)
94+
8395
console.print("[dim]API key (if configured) remains available for cloud project routing.[/dim]")
8496

8597

tests/cli/test_cloud_authentication.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,68 @@ def test_login_authentication_failure(self, monkeypatch):
199199
result = runner.invoke(app, ["cloud", "login"])
200200
assert result.exit_code == 1
201201
assert "Login failed" in result.stdout
202+
203+
204+
class TestLogoutCommand:
205+
"""Tests for `bm cloud logout`."""
206+
207+
@staticmethod
208+
def _patch(monkeypatch, default_workspace):
209+
class FakeConfig:
210+
cloud_client_id = "cid"
211+
cloud_domain = "https://auth.example.com"
212+
213+
def __init__(self):
214+
self.default_workspace = default_workspace
215+
216+
saved: list[FakeConfig] = []
217+
config_instance = FakeConfig()
218+
logout_called = {"value": False}
219+
220+
class FakeConfigManager:
221+
config = config_instance
222+
223+
def save_config(self, cfg):
224+
saved.append(cfg)
225+
226+
class FakeAuth:
227+
def __init__(self, **_kwargs):
228+
pass
229+
230+
def logout(self):
231+
logout_called["value"] = True
232+
233+
monkeypatch.setattr(
234+
"basic_memory.cli.commands.cloud.core_commands.ConfigManager", FakeConfigManager
235+
)
236+
monkeypatch.setattr("basic_memory.cli.commands.cloud.core_commands.CLIAuth", FakeAuth)
237+
return config_instance, saved, logout_called
238+
239+
def test_logout_clears_default_workspace(self, monkeypatch):
240+
"""Regression for #755: logout must invalidate the cached workspace."""
241+
config_instance, saved, logout_called = self._patch(
242+
monkeypatch, default_workspace="tenant-org-123"
243+
)
244+
runner = CliRunner()
245+
246+
result = runner.invoke(app, ["cloud", "logout"])
247+
248+
assert result.exit_code == 0
249+
assert logout_called["value"] is True
250+
assert config_instance.default_workspace is None
251+
# Save was called once because a non-None value needed clearing.
252+
assert len(saved) == 1
253+
assert saved[0].default_workspace is None
254+
255+
def test_logout_skips_save_when_no_default_workspace(self, monkeypatch):
256+
"""If nothing was cached, logout shouldn't rewrite the config file."""
257+
config_instance, saved, logout_called = self._patch(monkeypatch, default_workspace=None)
258+
runner = CliRunner()
259+
260+
result = runner.invoke(app, ["cloud", "logout"])
261+
262+
assert result.exit_code == 0
263+
assert logout_called["value"] is True
264+
assert config_instance.default_workspace is None
265+
# No save: avoid touching the file when there's nothing to clear.
266+
assert saved == []

0 commit comments

Comments
 (0)