Panniantong/Agent-Reach · error · GitHubConfigError

Agent Reach 的 GitHub 配置无法读取

Error message

Agent Reach 的 GitHub 配置无法读取

What it means

Raised by _explicit_github_credentials() when calling config.get('github_token') on the Agent Reach config object raises an unexpected exception. GH_TOKEN/GITHUB_TOKEN env vars and a None config are handled before this; only a config object whose .get() itself blows up (broken Mapping subclass, hostile __getitem__/__getattribute__, properties raising) reaches here.

Source

Thrown at agent_reach/channels/github.py:92

        return False
    if not isinstance(host, dict):
        raise GitHubConfigError("gh hosts.yml 的 github.com 配置无效")

    users = host.get("users")
    if users is not None and not isinstance(users, dict):
        raise GitHubConfigError("gh hosts.yml 的 users 配置无效")
    return bool(host.get("oauth_token") or host.get("user") or users)


def _explicit_github_credentials(config) -> bool:
    if any(os.environ.get(name) for name in ("GH_TOKEN", "GITHUB_TOKEN")):
        return True
    if config is None:
        return False
    try:
        return bool(config.get("github_token"))
    except Exception as exc:
        raise GitHubConfigError("Agent Reach 的 GitHub 配置无法读取") from exc


class GitHubChannel(Channel):
    name = "github"
    description = "GitHub 仓库和代码"
    backends = ["gh CLI"]
    tier = 0

    def can_handle(self, url: str) -> bool:
        from agent_reach.utils.url import host_matches

        return host_matches(url, "github.com")

    def check(self, config=None):
        self.active_backend = None
        probe = probe_command(
            "gh",
            ["--version"],

View on GitHub (pinned to 93ae1d18c3)

Solutions

  1. Pass the standard config object produced by agent_reach.config, or a plain dict
  2. If wrapping config, ensure get(key, default=None) is delegated to the underlying dict without raising
  3. Simpler: set GH_TOKEN/GITHUB_TOKEN in the environment — that path returns before config is touched

Example fix

# before
 channel.check(config=MyStrictConfig())  # .get() raises
# after
 channel.check(config={"github_token": "ghp_..."})  # or use agent_reach.config.load()
Defensive patterns

Strategy: type-guard

Validate before calling

def is_plain_mapping(cfg) -> bool:
    return isinstance(cfg, dict) or (hasattr(cfg, "get") and callable(cfg.get) and not isinstance(cfg, (str, list, set)))

Type guard

from typing import Any, Mapping

def is_safe_config(obj: Any) -> "typeguard[Mapping]":
    return isinstance(obj, Mapping)

Try / catch

from agent_reach.channels.github import GitHubConfigError
try:
    ok = _explicit_github_credentials(config)
except GitHubConfigError:
    ok = False  # or replace config with {} and retry once

Prevention

When it happens

Trigger: Passing a custom config object (not a plain dict / the YAML-backed Config class) whose get() raises — e.g. a dataclass without get, a Mapping that validates keys and throws, a mock raising in get. Env vars unset and config is not None.

Common situations: Embedding Agent Reach with a homegrown config wrapper; config loaded from a corrupted dataclass; test doubles that raise on unexpected attribute access.

Related errors


AI-assisted analysis of Panniantong/Agent-Reach@93ae1d18c3 (2026-08-14). Data as JSON: /api/errors/5166aca869dba1f3. Report an issue: GitHub.