diff --git a/src/openharness/ui/runtime.py b/src/openharness/ui/runtime.py index 5d56015..1ceff3b 100644 --- a/src/openharness/ui/runtime.py +++ b/src/openharness/ui/runtime.py @@ -198,13 +198,8 @@ def _resolve_api_client_from_settings(settings) -> SupportsStreamingMessages: def _safe_resolve_auth(): try: return settings.resolve_auth() - except (ValueError, Exception): - print( - "Error: No API key configured.\n" - " Run `oh auth login` to set up authentication, or set the\n" - " ANTHROPIC_API_KEY (or OPENAI_API_KEY) environment variable.", - file=sys.stderr, - ) + except Exception as exc: + _print_auth_resolution_error(settings, exc) raise SystemExit(1) if settings.api_format == "copilot": @@ -243,6 +238,39 @@ def _resolve_api_client_from_settings(settings) -> SupportsStreamingMessages: ) +def _print_auth_resolution_error(settings, exc: Exception) -> None: + """Render auth failures without collapsing subscription errors into API-key advice.""" + try: + profile_name, profile = settings.resolve_profile() + auth_source = (getattr(profile, "auth_source", "") or "").strip() + except Exception: + profile_name = "" + auth_source = "" + + message = str(exc).strip() or exc.__class__.__name__ + if auth_source in {"claude_subscription", "codex_subscription"}: + login_command = "claude-login" if auth_source == "claude_subscription" else "codex-login" + provider_name = profile_name or ( + "claude-subscription" if auth_source == "claude_subscription" else "codex" + ) + print( + f"Error: {message}\n" + f" This profile uses subscription auth, not an API key.\n" + f" Run `oh auth {login_command}` to bind the local CLI session, then\n" + f" run `oh provider use {provider_name}` to activate it.", + file=sys.stderr, + ) + return + + print( + "Error: No API key configured.\n" + f" {message}\n" + " Run `oh auth login` to set up authentication, or set the\n" + " ANTHROPIC_API_KEY (or OPENAI_API_KEY) environment variable.", + file=sys.stderr, + ) + + async def build_runtime( *, prompt: str | None = None, diff --git a/tests/test_ui/test_runtime_api_key.py b/tests/test_ui/test_runtime_api_key.py index 825b9a5..747305e 100644 --- a/tests/test_ui/test_runtime_api_key.py +++ b/tests/test_ui/test_runtime_api_key.py @@ -31,3 +31,19 @@ async def test_build_runtime_exits_cleanly_for_openai_format(monkeypatch): with pytest.raises(SystemExit, match="1"): await build_runtime(active_profile="openai-compatible", api_format="openai") + + +@pytest.mark.asyncio +async def test_build_runtime_reports_subscription_auth_setup(monkeypatch, tmp_path, capsys): + """Subscription profiles should not be reported as missing API keys.""" + monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path / "config")) + monkeypatch.delenv("ANTHROPIC_AUTH_TOKEN", raising=False) + + with pytest.raises(SystemExit, match="1"): + await build_runtime(active_profile="claude-subscription") + + captured = capsys.readouterr() + assert "subscription auth, not an API key" in captured.err + assert "oh auth claude-login" in captured.err + assert "oh provider use claude-subscription" in captured.err + assert "No API key configured" not in captured.err