{"record":{"id":"5166aca869dba1f3","repo":"Panniantong/Agent-Reach","slug":"agent-reach-github","errorCode":null,"errorMessage":"Agent Reach 的 GitHub 配置无法读取","messagePattern":"Agent Reach 的 GitHub 配置无法读取","errorType":"exception","errorClass":"GitHubConfigError","httpStatus":null,"severity":"error","filePath":"agent_reach/channels/github.py","lineNumber":92,"sourceCode":"        return False\n    if not isinstance(host, dict):\n        raise GitHubConfigError(\"gh hosts.yml 的 github.com 配置无效\")\n\n    users = host.get(\"users\")\n    if users is not None and not isinstance(users, dict):\n        raise GitHubConfigError(\"gh hosts.yml 的 users 配置无效\")\n    return bool(host.get(\"oauth_token\") or host.get(\"user\") or users)\n\n\ndef _explicit_github_credentials(config) -> bool:\n    if any(os.environ.get(name) for name in (\"GH_TOKEN\", \"GITHUB_TOKEN\")):\n        return True\n    if config is None:\n        return False\n    try:\n        return bool(config.get(\"github_token\"))\n    except Exception as exc:\n        raise GitHubConfigError(\"Agent Reach 的 GitHub 配置无法读取\") from exc\n\n\nclass GitHubChannel(Channel):\n    name = \"github\"\n    description = \"GitHub 仓库和代码\"\n    backends = [\"gh CLI\"]\n    tier = 0\n\n    def can_handle(self, url: str) -> bool:\n        from agent_reach.utils.url import host_matches\n\n        return host_matches(url, \"github.com\")\n\n    def check(self, config=None):\n        self.active_backend = None\n        probe = probe_command(\n            \"gh\",\n            [\"--version\"],","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/Panniantong/Agent-Reach/blob/93ae1d18c37b707dec053c7c4f9d91cd8ef8943d/agent_reach/channels/github.py#L74-L110","documentation":"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.","triggerScenarios":"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.","commonSituations":"Embedding Agent Reach with a homegrown config wrapper; config loaded from a corrupted dataclass; test doubles that raise on unexpected attribute access.","solutions":["Pass the standard config object produced by agent_reach.config, or a plain dict","If wrapping config, ensure get(key, default=None) is delegated to the underlying dict without raising","Simpler: set GH_TOKEN/GITHUB_TOKEN in the environment — that path returns before config is touched"],"exampleFix":"# before\n channel.check(config=MyStrictConfig())  # .get() raises\n# after\n channel.check(config={\"github_token\": \"ghp_...\"})  # or use agent_reach.config.load()","handlingStrategy":"type-guard","validationCode":"def is_plain_mapping(cfg) -> bool:\n    return isinstance(cfg, dict) or (hasattr(cfg, \"get\") and callable(cfg.get) and not isinstance(cfg, (str, list, set)))","typeGuard":"from typing import Any, Mapping\n\ndef is_safe_config(obj: Any) -> \"typeguard[Mapping]\":\n    return isinstance(obj, Mapping)","tryCatchPattern":"from agent_reach.channels.github import GitHubConfigError\ntry:\n    ok = _explicit_github_credentials(config)\nexcept GitHubConfigError:\n    ok = False  # or replace config with {} and retry once","preventionTips":["Pass plain dicts or agent_reach.config's object to channel APIs","Prefer GH_TOKEN/GITHUB_TOKEN env vars over config plumbing","Never hand channel.check() objects that validate/raise on get()"],"tags":["github","config","api-misuse"],"backgroundTag":null,"analyzedSha":"93ae1d18c37b707dec053c7c4f9d91cd8ef8943d","analyzedAt":"2026-08-14T22:54:06.735Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}