fix: gate Claude Code credentials behind explicit Hermes config in wizard trigger (#4210)
If a user has Claude Code installed but never configured Hermes, the first-run guard found those external credentials and skipped the setup wizard. Users got silently routed to someone else's inference without being asked. Now _has_any_provider_configured() checks whether Hermes itself has been explicitly configured (model in config differs from hardcoded default) before counting Claude Code credentials. Fresh installs trigger the wizard regardless of what external tools are on the machine. Salvaged from PR #4194 by sudoingX — wizard trigger fix only. Model auto-detect change under separate review. Co-authored-by: Xpress AI (Dip KD) <200180104+sudoingX@users.noreply.github.com>
This commit is contained in:
@@ -173,9 +173,25 @@ def _relative_time(ts) -> str:
|
||||
|
||||
def _has_any_provider_configured() -> bool:
|
||||
"""Check if at least one inference provider is usable."""
|
||||
from hermes_cli.config import get_env_path, get_hermes_home
|
||||
from hermes_cli.config import get_env_path, get_hermes_home, load_config
|
||||
from hermes_cli.auth import get_auth_status
|
||||
|
||||
# Determine whether Hermes itself has been explicitly configured (model
|
||||
# in config that isn't the hardcoded default). Used below to gate external
|
||||
# tool credentials (Claude Code, Codex CLI) that shouldn't silently skip
|
||||
# the setup wizard on a fresh install.
|
||||
from hermes_cli.config import DEFAULT_CONFIG
|
||||
_DEFAULT_MODEL = DEFAULT_CONFIG.get("model", "")
|
||||
cfg = load_config()
|
||||
model_cfg = cfg.get("model")
|
||||
if isinstance(model_cfg, dict):
|
||||
_model_name = (model_cfg.get("default") or "").strip()
|
||||
elif isinstance(model_cfg, str):
|
||||
_model_name = model_cfg.strip()
|
||||
else:
|
||||
_model_name = ""
|
||||
_has_hermes_config = _model_name and _model_name != _DEFAULT_MODEL
|
||||
|
||||
# Check env vars (may be set by .env or shell).
|
||||
# OPENAI_BASE_URL alone counts — local models (vLLM, llama.cpp, etc.)
|
||||
# often don't require an API key.
|
||||
@@ -231,15 +247,16 @@ def _has_any_provider_configured() -> bool:
|
||||
|
||||
|
||||
# Check for Claude Code OAuth credentials (~/.claude/.credentials.json)
|
||||
# These are used by resolve_anthropic_token() at runtime but were missing
|
||||
# from this startup gate check.
|
||||
try:
|
||||
from agent.anthropic_adapter import read_claude_code_credentials, is_claude_code_token_valid
|
||||
creds = read_claude_code_credentials()
|
||||
if creds and (is_claude_code_token_valid(creds) or creds.get("refreshToken")):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
# Only count these if Hermes has been explicitly configured — Claude Code
|
||||
# being installed doesn't mean the user wants Hermes to use their tokens.
|
||||
if _has_hermes_config:
|
||||
try:
|
||||
from agent.anthropic_adapter import read_claude_code_credentials, is_claude_code_token_valid
|
||||
creds = read_claude_code_credentials()
|
||||
if creds and (is_claude_code_token_valid(creds) or creds.get("refreshToken")):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@@ -622,6 +622,57 @@ class TestHasAnyProviderConfigured:
|
||||
from hermes_cli.main import _has_any_provider_configured
|
||||
assert _has_any_provider_configured() is True
|
||||
|
||||
def test_claude_code_creds_ignored_on_fresh_install(self, monkeypatch, tmp_path):
|
||||
"""Claude Code credentials should NOT skip the wizard when Hermes is unconfigured."""
|
||||
from hermes_cli import config as config_module
|
||||
hermes_home = tmp_path / ".hermes"
|
||||
hermes_home.mkdir()
|
||||
monkeypatch.setattr(config_module, "get_env_path", lambda: hermes_home / ".env")
|
||||
monkeypatch.setattr(config_module, "get_hermes_home", lambda: hermes_home)
|
||||
# Clear all provider env vars so earlier checks don't short-circuit
|
||||
for var in ("OPENROUTER_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY",
|
||||
"ANTHROPIC_TOKEN", "OPENAI_BASE_URL"):
|
||||
monkeypatch.delenv(var, raising=False)
|
||||
# Simulate valid Claude Code credentials
|
||||
monkeypatch.setattr(
|
||||
"agent.anthropic_adapter.read_claude_code_credentials",
|
||||
lambda: {"accessToken": "sk-ant-test", "refreshToken": "ref-tok"},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"agent.anthropic_adapter.is_claude_code_token_valid",
|
||||
lambda creds: True,
|
||||
)
|
||||
from hermes_cli.main import _has_any_provider_configured
|
||||
assert _has_any_provider_configured() is False
|
||||
|
||||
def test_claude_code_creds_counted_when_hermes_configured(self, monkeypatch, tmp_path):
|
||||
"""Claude Code credentials should count when Hermes has been explicitly configured."""
|
||||
import yaml
|
||||
from hermes_cli import config as config_module
|
||||
hermes_home = tmp_path / ".hermes"
|
||||
hermes_home.mkdir()
|
||||
# Write a config with a non-default model to simulate explicit configuration
|
||||
config_file = hermes_home / "config.yaml"
|
||||
config_file.write_text(yaml.dump({"model": {"default": "my-local-model"}}))
|
||||
monkeypatch.setattr(config_module, "get_env_path", lambda: hermes_home / ".env")
|
||||
monkeypatch.setattr(config_module, "get_hermes_home", lambda: hermes_home)
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
# Clear all provider env vars
|
||||
for var in ("OPENROUTER_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY",
|
||||
"ANTHROPIC_TOKEN", "OPENAI_BASE_URL"):
|
||||
monkeypatch.delenv(var, raising=False)
|
||||
# Simulate valid Claude Code credentials
|
||||
monkeypatch.setattr(
|
||||
"agent.anthropic_adapter.read_claude_code_credentials",
|
||||
lambda: {"accessToken": "sk-ant-test", "refreshToken": "ref-tok"},
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"agent.anthropic_adapter.is_claude_code_token_valid",
|
||||
lambda creds: True,
|
||||
)
|
||||
from hermes_cli.main import _has_any_provider_configured
|
||||
assert _has_any_provider_configured() is True
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Kimi Code auto-detection tests
|
||||
|
||||
Reference in New Issue
Block a user