fix(doctor): check gh auth status when GITHUB_TOKEN absent

hermes doctor showed 'No GITHUB_TOKEN (60 req/hr)' warning even when
users had authenticated via gh auth login. Now falls back to
gh auth status --json authenticated when GITHUB_TOKEN and GH_TOKEN
are both unset.

Fixes #16115
This commit is contained in:
jjjojoj 2026-04-27 00:13:46 +08:00 committed by Teknium
parent 8ab9f61dcf
commit 103f51ad34
2 changed files with 90 additions and 0 deletions

View file

@ -1264,9 +1264,23 @@ def run_doctor(args):
check_warn("Skills Hub directory not initialized", "(run: hermes skills list)") check_warn("Skills Hub directory not initialized", "(run: hermes skills list)")
from hermes_cli.config import get_env_value from hermes_cli.config import get_env_value
def _gh_authenticated() -> bool:
"""Check if gh CLI is authenticated via token file or device flow."""
try:
result = subprocess.run(
["gh", "auth", "status", "--json", "authenticated"],
capture_output=True, timeout=10,
)
return result.returncode == 0
except (FileNotFoundError, subprocess.TimeoutExpired):
return False
github_token = get_env_value("GITHUB_TOKEN") or get_env_value("GH_TOKEN") github_token = get_env_value("GITHUB_TOKEN") or get_env_value("GH_TOKEN")
if github_token: if github_token:
check_ok("GitHub token configured (authenticated API access)") check_ok("GitHub token configured (authenticated API access)")
elif _gh_authenticated():
check_ok("GitHub authenticated via gh CLI", "(full API access — no GITHUB_TOKEN needed)")
else: else:
check_warn("No GITHUB_TOKEN", f"(60 req/hr rate limit — set in {_DHH}/.env for better rates)") check_warn("No GITHUB_TOKEN", f"(60 req/hr rate limit — set in {_DHH}/.env for better rates)")

View file

@ -663,3 +663,79 @@ def test_run_doctor_opencode_go_skips_invalid_models_probe(monkeypatch, tmp_path
) )
assert not any(url == "https://opencode.ai/zen/go/v1/models" for url, _, _ in calls) assert not any(url == "https://opencode.ai/zen/go/v1/models" for url, _, _ in calls)
assert not any("opencode" in url.lower() and "models" in url.lower() for url, _, _ in calls) assert not any("opencode" in url.lower() and "models" in url.lower() for url, _, _ in calls)
class TestGitHubTokenCheck:
"""Tests for GitHub token / gh auth detection in doctor."""
def test_no_token_and_not_gh_authenticated_shows_warn(self, monkeypatch, tmp_path):
home = tmp_path / ".hermes"
home.mkdir(parents=True, exist_ok=True)
monkeypatch.setenv("HERMES_HOME", str(home))
monkeypatch.setenv("PATH", "/nonexistent") # gh not found
from hermes_cli.doctor import run_doctor, _DHH
import io, contextlib
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
run_doctor(Namespace(fix=False))
out = buf.getvalue()
assert "No GITHUB_TOKEN" in out
assert "60 req/hr" in out
def test_token_env_present_shows_ok(self, monkeypatch, tmp_path):
home = tmp_path / ".hermes"
home.mkdir(parents=True, exist_ok=True)
monkeypatch.setenv("HERMES_HOME", str(home))
monkeypatch.setenv("GITHUB_TOKEN", "ghp_test123")
monkeypatch.setenv("PATH", "/nonexistent") # gh not found
from hermes_cli.doctor import run_doctor
import io, contextlib
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
run_doctor(Namespace(fix=False))
out = buf.getvalue()
assert "GitHub token configured" in out
def test_gh_authenticated_without_env_token_shows_ok(self, monkeypatch, tmp_path):
home = tmp_path / ".hermes"
home.mkdir(parents=True, exist_ok=True)
monkeypatch.setenv("HERMES_HOME", str(home))
# No GITHUB_TOKEN or GH_TOKEN
monkeypatch.delenv("GITHUB_TOKEN", raising=False)
monkeypatch.delenv("GH_TOKEN", raising=False)
# Mock gh to return success
import shutil
real_which = shutil.which
def mock_which(cmd):
return "/usr/local/bin/gh" if cmd == "gh" else real_which(cmd)
monkeypatch.setattr(shutil, "which", mock_which)
call_log = []
def mock_run(cmd, **kwargs):
call_log.append(cmd)
if cmd[:2] == ["gh", "auth"]:
result = types.SimpleNamespace(returncode=0, stdout="", stderr="")
else:
result = types.SimpleNamespace(returncode=1, stdout="", stderr="")
return result
import subprocess
monkeypatch.setattr(subprocess, "run", mock_run)
from hermes_cli.doctor import run_doctor
import io, contextlib
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
run_doctor(Namespace(fix=False))
out = buf.getvalue()
assert "gh auth" in str(call_log) or any(c[0] == "gh" for c in call_log), f"gh not called: {call_log}"
assert "GitHub authenticated via gh CLI" in out or "token configured" in out