From 99cf8c4950cd86eaf2855332cd7218c97bb13ea2 Mon Sep 17 00:00:00 2001 From: Samir Hanna Verza <123646616+samirhvbr@users.noreply.github.com> Date: Wed, 28 Oct 2026 19:35:53 -0300 Subject: [PATCH 1/2] fix(cloud): report scope metadata persistence failures --- strix/interface/cloud/session.py | 15 ++++++- tests/test_cloud_session.py | 68 ++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/strix/interface/cloud/session.py b/strix/interface/cloud/session.py index d2ec0861f3..98fb548c1d 100644 --- a/strix/interface/cloud/session.py +++ b/strix/interface/cloud/session.py @@ -126,7 +126,20 @@ def _run_scopes_set(console: Console, argv: list[str]) -> int: if key in result } ) - save_record(stored) + try: + save_record(stored) + except OSError as exc: + return _error( + console, + http.CloudError( + "the platform updated the session scopes, but the local session " + f"metadata could not be stored: {exc}. Check the active scopes with " + "`strix cloud session show`; fix local storage and rerun the same " + "scopes set command to refresh the stored metadata.", + payload={"scopes_updated": True, "local_record_updated": False}, + ), + as_json=as_json, + ) if as_json: emit(console, result, as_json=True) else: diff --git a/tests/test_cloud_session.py b/tests/test_cloud_session.py index 70349c1abe..8789a36aad 100644 --- a/tests/test_cloud_session.py +++ b/tests/test_cloud_session.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import sys from typing import TYPE_CHECKING, Any import pytest @@ -126,6 +127,73 @@ def test_logout_keeps_local_token_when_remote_outcome_is_not_definitive( assert json.loads(capsys.readouterr().out)["removed"] is False +@pytest.mark.parametrize("as_json", [False, True]) +def test_session_scope_update_reports_local_storage_failure( + auth_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: Any, as_json: bool +) -> None: + platform_cli.save_record( + { + "api_token": "secret", + "organization_id": "org_1", + "app_url": "https://app.example.test", + "scopes": ["scans:read", "scans:write"], + } + ) + before = auth_path.read_text(encoding="utf-8") + calls: list[tuple[str, str]] = [] + + def request(method: str, path: str, **_kwargs: Any) -> Response: + calls.append((method, path)) + return Response({"scopes": ["scans:read"], "scope_profile": "custom"}) + + def fail_save(_record: dict[str, Any]) -> None: + raise OSError("disk full") + + monkeypatch.setattr(http, "request", request) + monkeypatch.setattr(cloud_session, "save_record", fail_save) + monkeypatch.setattr(sys.stdout, "isatty", lambda: True) + + argv = ["session", "scopes", "set", "--scopes", "scans:read"] + assert cloud.run_cloud([*argv, *(["--json"] if as_json else [])]) == 1 + output = capsys.readouterr().out + if as_json: + payload = json.loads(output) + assert payload["scopes_updated"] is True + assert payload["local_record_updated"] is False + output = payload["error"] + assert "disk full" in output + assert "session show" in output + assert "platform updated the session scopes" in output + assert calls == [("PATCH", "/cli/session")] + assert auth_path.read_text(encoding="utf-8") == before + + +@pytest.mark.parametrize("environment_token", [False, True]) +def test_session_scope_update_override_does_not_need_writable_local_storage( + auth_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: Any, environment_token: bool +) -> None: + platform_cli.save_record({"api_token": "stored-secret", "scopes": ["scans:read"]}) + before = auth_path.read_text(encoding="utf-8") + monkeypatch.setattr( + http, + "request", + lambda *_args, **_kwargs: Response({"scopes": ["scans:read"], "scope_profile": "custom"}), + ) + + def fail_save(_record: dict[str, Any]) -> None: + pytest.fail("an override session must not update the local record") + + monkeypatch.setattr(cloud_session, "save_record", fail_save) + token_args = ["--token", "override"] + if environment_token: + monkeypatch.setenv("STRIX_API_TOKEN", "override") + token_args = [] + argv = ["session", "scopes", "set", "--scopes", "scans:read", "--json", *token_args] + assert cloud.run_cloud(argv) == 0 + assert json.loads(capsys.readouterr().out)["scopes"] == ["scans:read"] + assert auth_path.read_text(encoding="utf-8") == before + + def test_local_only_logout_is_explicit_and_recoverable(auth_path: Path, capsys: Any) -> None: platform_cli.save_record({"api_token": "secret"}) assert cloud.run_cloud(["logout", "--local-only", "--json"]) == 0 From f13c192ff724932d54eb9a11d28fb999557f1ee1 Mon Sep 17 00:00:00 2001 From: Samir Hanna Verza <123646616+samirhvbr@users.noreply.github.com> Date: Wed, 28 Oct 2026 19:45:18 -0300 Subject: [PATCH 2/2] fix(cloud): show effective scopes in recovery instructions --- strix/interface/cloud/session.py | 2 +- tests/test_cloud_session.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/strix/interface/cloud/session.py b/strix/interface/cloud/session.py index 98fb548c1d..519e1abd9f 100644 --- a/strix/interface/cloud/session.py +++ b/strix/interface/cloud/session.py @@ -134,7 +134,7 @@ def _run_scopes_set(console: Console, argv: list[str]) -> int: http.CloudError( "the platform updated the session scopes, but the local session " f"metadata could not be stored: {exc}. Check the active scopes with " - "`strix cloud session show`; fix local storage and rerun the same " + "`strix cloud session scopes`; fix local storage and rerun the same " "scopes set command to refresh the stored metadata.", payload={"scopes_updated": True, "local_record_updated": False}, ), diff --git a/tests/test_cloud_session.py b/tests/test_cloud_session.py index 8789a36aad..bca98caf5b 100644 --- a/tests/test_cloud_session.py +++ b/tests/test_cloud_session.py @@ -162,7 +162,7 @@ def fail_save(_record: dict[str, Any]) -> None: assert payload["local_record_updated"] is False output = payload["error"] assert "disk full" in output - assert "session show" in output + assert "session scopes" in output assert "platform updated the session scopes" in output assert calls == [("PATCH", "/cli/session")] assert auth_path.read_text(encoding="utf-8") == before