{"record":{"id":"a95b37f661602c8e","repo":"Panniantong/Agent-Reach","slug":"gh-hosts-yml","errorCode":null,"errorMessage":"gh hosts.yml 无法安全读取","messagePattern":"gh hosts\\.yml 无法安全读取","errorType":"exception","errorClass":"GitHubConfigError","httpStatus":null,"severity":"error","filePath":"agent_reach/channels/github.py","lineNumber":60,"sourceCode":"\n    if os.name == \"nt\":\n        app_data = os.environ.get(\"APPDATA\")\n        if app_data:\n            return Path(app_data) / \"GitHub CLI\" / \"hosts.yml\"\n\n    return Path.home() / \".config\" / \"gh\" / \"hosts.yml\"\n\n\ndef _saved_github_host_configured() -> bool:\n    \"\"\"Inspect github.com's hosts.yml entry without executing gh.\"\"\"\n    hosts_path = _gh_hosts_path()\n    try:\n        raw = read_small_text_no_follow(\n            hosts_path,\n            max_bytes=_MAX_HOSTS_BYTES,\n        )\n    except (OSError, PrivatePathError, UnicodeError) as exc:\n        raise GitHubConfigError(\"gh hosts.yml 无法安全读取\") from exc\n    if raw is None:\n        return False\n    try:\n        payload = yaml.safe_load(raw)\n    except yaml.YAMLError as exc:\n        raise GitHubConfigError(\"gh hosts.yml 不是有效的 UTF-8 YAML\") from exc\n    if payload is None:\n        return False\n    if not isinstance(payload, dict):\n        raise GitHubConfigError(\"gh hosts.yml 顶层必须是对象\")\n\n    host = payload.get(\"github.com\")\n    if host is None:\n        return False\n    if not isinstance(host, dict):\n        raise GitHubConfigError(\"gh hosts.yml 的 github.com 配置无效\")\n\n    users = host.get(\"users\")","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/Panniantong/Agent-Reach/blob/93ae1d18c37b707dec053c7c4f9d91cd8ef8943d/agent_reach/channels/github.py#L42-L78","documentation":"Raised by _saved_github_host_configured() when the gh CLI's ~/.config/gh/hosts.yml cannot be safely read. Reading goes through read_small_text_no_follow(), which refuses symlinked path components, non-regular files, files over _MAX_HOSTS_BYTES, and raises OSError/UnicodeError for other I/O or UTF-8 decode failures. Any of these makes the GitHub channel's credential probe fail closed with GitHubConfigError.","triggerScenarios":"Calling GitHubChannel.check()/doctor when: hosts.yml is a symlink or sits on a symlinked directory; the file is larger than _MAX_HOSTS_BYTES; the file is a FIFO/device; permissions deny read (OSError/EPERM); or the file contains bytes that are not valid UTF-8 (UnicodeError).","commonSituations":"Users manage dotfiles via symlinks (stow, chezmoi) so ~/.config/gh or hosts.yml is a link; hosts.yml got truncated/corrupted; disk-full or permission changes after a system migration; a non-UTF-8 editor wrote the file.","solutions":["Inspect ~/.config/gh/hosts.yml: ensure it is a real file with no symlinks in its path (ls -la ~/.config/gh/) and replace symlinks with real files or bind mounts","Check permissions: chmod 600 ~/.config/gh/hosts.yml and confirm you own it","If the file is corrupt, run `gh auth login` again to regenerate a clean hosts.yml","If you auth via token instead, export GH_TOKEN or GITHUB_TOKEN — _explicit_github_credentials() is checked before this path is relied on","As a last resort remove hosts.yml; read returns None and the check reports 'not configured' instead of raising"],"exampleFix":"# before: ~/.config/gh/hosts.yml is a symlink to ~/dotfiles/gh-hosts.yml\n# after: real file\n cp ~/dotfiles/gh-hosts.yml ~/.config/gh/hosts.yml && chmod 600 ~/.config/gh/hosts.yml","handlingStrategy":"try-catch","validationCode":"from pathlib import Path\nimport os\n\ndef hosts_readable() -> bool:\n    p = Path.home() / \".config\" / \"gh\" / \"hosts.yml\"\n    if not p.exists() or p.is_symlink() or p.parent.is_symlink():\n        return False\n    try:\n        st = p.stat()\n        return st.st_size <= 64 * 1024 and os.access(p, os.R_OK)\n    except OSError:\n        return False","typeGuard":null,"tryCatchPattern":"from agent_reach.channels.github import GitHubConfigError\ntry:\n    configured = _saved_github_host_configured()\nexcept GitHubConfigError as exc:\n    # degrade to 'not configured', surface hint to re-run gh auth login\n    report_config_hint(str(exc))\nelse:\n    use(configured)","preventionTips":["Never symlink gh config paths; keep ~/.config/gh as a real directory","Run `gh auth login` instead of hand-writing hosts.yml","Set GH_TOKEN/GITHUB_TOKEN so the env-var path short-circuits the file probe"],"tags":["github","gh-cli","config","filesystem","security"],"backgroundTag":null,"analyzedSha":"93ae1d18c37b707dec053c7c4f9d91cd8ef8943d","analyzedAt":"2026-08-14T22:54:06.735Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}