From 33c1a5b603c9477736a343eae3376a24b4013d87 Mon Sep 17 00:00:00 2001 From: bearsyankees Date: Fri, 28 Aug 2026 15:32:09 -0400 Subject: [PATCH] Preserve CLI session metadata in JSON output --- strix/interface/cloud/session.py | 8 +++++++- strix/interface/cloud/workspaces.py | 8 ++++++++ tests/test_cloud_cli.py | 10 ++++++++-- tests/test_cloud_session.py | 21 +++++++++++++++++++++ 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/strix/interface/cloud/session.py b/strix/interface/cloud/session.py index 5089bdce..d2ec0861 100644 --- a/strix/interface/cloud/session.py +++ b/strix/interface/cloud/session.py @@ -147,7 +147,13 @@ def _string_list(value: Any) -> list[str]: def _error(console: Console, error: http.CloudError, *, as_json: bool) -> int: if as_json: - emit(console, {"error": str(error)}, as_json=True) + raw_payload: Any = error.payload + error_payload = cast("dict[str, Any]", raw_payload) + payload = dict(error_payload) if isinstance(raw_payload, dict) else {} + payload["error"] = str(error) + if payload.get("detail") == payload.get("error"): + payload.pop("detail", None) + emit(console, payload, as_json=True) else: console.print(f"[red]Error:[/] {escape(sanitize_terminal_text(error))}") return error.exit_code diff --git a/strix/interface/cloud/workspaces.py b/strix/interface/cloud/workspaces.py index 40b558b9..1d495357 100644 --- a/strix/interface/cloud/workspaces.py +++ b/strix/interface/cloud/workspaces.py @@ -169,6 +169,14 @@ def _use( # noqa: PLR0912, PLR0915 "workspace_id": record["organization_id"], "workspace_name": record["organization_name"], "scopes": record["scopes"], + "requested_scopes": record.get("requested_scopes", record["scopes"]), + "scope_ceiling": record.get("scope_ceiling", []), + "scope_profile": record.get("scope_profile", "custom"), + "expires_at": record.get("expires_at"), + "token_id": record.get("token_id"), + "credential_source": record.get("credential_source", "api"), + "device_name": record.get("device_name"), + "stored": not external_token, } if as_json: emit(console, result, as_json=True) diff --git a/tests/test_cloud_cli.py b/tests/test_cloud_cli.py index 6e5c2613..f2c23435 100644 --- a/tests/test_cloud_cli.py +++ b/tests/test_cloud_cli.py @@ -1685,11 +1685,14 @@ def test_workspaces_use_switches_stored_token( assert record["api_token"] == "old" assert record["organization_name"] == "Team One" assert record["email"] == "a@b.test" - assert "org_1" in capsys.readouterr().out + output = json.loads(capsys.readouterr().out) + assert output["workspace_id"] == "org_1" + assert output["scope_profile"] == "custom" + assert output["stored"] is True def test_workspace_use_explicit_token_starts_with_fresh_account_state( - monkeypatch: pytest.MonkeyPatch, tmp_path: Path + monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: Any ) -> None: auth_path = tmp_path / "platform-auth.json" monkeypatch.setattr(platform_cli, "AUTH_PATH", auth_path) @@ -1733,6 +1736,9 @@ def test_workspace_use_explicit_token_starts_with_fresh_account_state( assert record["api_token"] == "account-a-token" assert record["organization_id"] == "org_a" assert record["email"] == "account-a@example.test" + output = json.loads(capsys.readouterr().out) + assert output["workspace_id"] == "org_b" + assert output["stored"] is False def test_workspace_use_environment_token_starts_with_fresh_account_state( diff --git a/tests/test_cloud_session.py b/tests/test_cloud_session.py index 5a4d1ea1..aecda688 100644 --- a/tests/test_cloud_session.py +++ b/tests/test_cloud_session.py @@ -6,9 +6,11 @@ import json from typing import TYPE_CHECKING, Any import pytest +from rich.console import Console from strix.interface import cloud, platform_cli, platform_identity from strix.interface.cloud import http +from strix.interface.cloud import session as cloud_session if TYPE_CHECKING: @@ -159,6 +161,25 @@ def test_local_only_logout_is_explicit_and_recoverable(auth_path: Path, capsys: assert not auth_path.exists() +def test_session_json_errors_preserve_machine_readable_server_details(capsys: Any) -> None: + error = http.CloudError( + "workspace changed", + payload={ + "detail": "workspace changed", + "code": "workspace_session_changed", + "current_organization_id": "org_current", + }, + ) + + assert cloud_session._error(Console(), error, as_json=True) == http.EXIT_ERROR + payload = json.loads(capsys.readouterr().out) + assert payload == { + "code": "workspace_session_changed", + "current_organization_id": "org_current", + "error": "workspace changed", + } + + def test_cli_device_identity_is_stable_and_privacy_safe( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: