Panniantong/Agent-Reach · error · GitHubConfigError

gh hosts.yml 顶层必须是对象

Error message

gh hosts.yml 顶层必须是对象

What it means

Raised when yaml.safe_load() succeeds but the top-level YAML value is not a mapping — e.g. the file contains a plain scalar, a list, or a multi-document stream whose first node is not a dict. gh's hosts.yml must be a map keyed by hostname.

Source

Thrown at agent_reach/channels/github.py:70

    """Inspect github.com's hosts.yml entry without executing gh."""
    hosts_path = _gh_hosts_path()
    try:
        raw = read_small_text_no_follow(
            hosts_path,
            max_bytes=_MAX_HOSTS_BYTES,
        )
    except (OSError, PrivatePathError, UnicodeError) as exc:
        raise GitHubConfigError("gh hosts.yml 无法安全读取") from exc
    if raw is None:
        return False
    try:
        payload = yaml.safe_load(raw)
    except yaml.YAMLError as exc:
        raise GitHubConfigError("gh hosts.yml 不是有效的 UTF-8 YAML") from exc
    if payload is None:
        return False
    if not isinstance(payload, dict):
        raise GitHubConfigError("gh hosts.yml 顶层必须是对象")

    host = payload.get("github.com")
    if host is None:
        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

View on GitHub (pinned to 93ae1d18c3)

Solutions

  1. Rewrite hosts.yml to the correct shape: a top-level mapping with hostname keys (github.com: {oauth_token: ..., user: ...})
  2. Prefer `gh auth login` over hand-editing
  3. If YAML anchors/double documents are used, flatten to a single mapping document

Example fix

# before
 gho_abc123...
# after
 github.com:
   oauth_token: gho_abc123...
   user: myname
Defensive patterns

Strategy: validation

Validate before calling

import yaml

def hosts_toplevel_is_map(path) -> bool:
    try:
        payload = yaml.safe_load(open(path, encoding="utf-8").read())
    except Exception:
        return False
    return payload is None or isinstance(payload, dict)

Try / catch

try:
    _saved_github_host_configured()
except GitHubConfigError:
    treat_github_as_unconfigured()

Prevention

When it happens

Trigger: hosts.yml contains just a token string ('gho_xxx'), a YAML list ('- github.com'), or '---\njust some text'. safe_load returns str/list, isinstance(payload, dict) fails, error raised. Note: empty file → payload is None → returns False (no error).

Common situations: Users echo a raw token into hosts.yml instead of using gh auth login; a template placeholder like '${TOKEN}' left unreplaced yields a scalar; accidental shell redirect truncation to a stray word.

Related errors


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