{"record":{"id":"ebb5de4c0cd4a37e","repo":"Panniantong/Agent-Reach","slug":"xfetch-json-object","errorCode":null,"errorMessage":"xfetch 会话文件必须是 JSON object","messagePattern":"xfetch 会话文件必须是 JSON object","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"agent_reach/cookie_extract.py","lineNumber":348,"sourceCode":"\n    return results\n\n\ndef _read_xfetch_session(path: Path) -> dict:\n    \"\"\"Read a small regular legacy session file without following symlinks.\"\"\"\n    import json\n\n    from agent_reach.utils.paths import read_small_text_no_follow\n\n    payload = read_small_text_no_follow(\n        path,\n        max_bytes=_MAX_XFETCH_SESSION_BYTES,\n    )\n    if payload is None:\n        return {}\n    loaded = json.loads(payload)\n    if not isinstance(loaded, dict):\n        raise ValueError(\"xfetch 会话文件必须是 JSON object\")\n    return loaded\n\n\ndef _sync_xfetch_session(auth_token: str, ct0: str) -> bool:\n    \"\"\"Sync Twitter credentials to ~/.config/xfetch/session.json (legacy xreach compat).\"\"\"\n    import json\n\n    try:\n        from agent_reach.utils.paths import (\n            atomic_write_private_text,\n            home_dir,\n            make_private_dir,\n        )\n\n        xfetch_dir = home_dir() / \".config\" / \"xfetch\"\n        make_private_dir(xfetch_dir)\n        session_path = Path(xfetch_dir) / \"session.json\"\n        session_data = _read_xfetch_session(session_path)","sourceCodeStart":330,"sourceCodeEnd":366,"githubUrl":"https://github.com/Panniantong/Agent-Reach/blob/93ae1d18c37b707dec053c7c4f9d91cd8ef8943d/agent_reach/cookie_extract.py#L330-L366","documentation":"Raised in agent_reach/cookie_extract.py when the xfetch session file (~/.config/xfetch/session.json, capped at 64 KiB by _MAX_XFETCH_SESSION_BYTES) parses as valid JSON but is not an object — e.g. it is a list, string, or number. The loader (used for legacy xreach compatibility) requires a top-level JSON object mapping config keys to values. Message 'xfetch 会话文件必须是 JSON object' means 'the xfetch session file must be a JSON object'.","triggerScenarios":"A hand-edited or tool-written session.json whose top level is a JSON array or scalar; migrating from a tool that serializes sessions as [ {...} ].","commonSituations":"Users editing session.json manually and wrapping it in a list; another tool overwriting the file with a different schema; truncated/rewritten exports.","solutions":["Rewrite ~/.config/xfetch/session.json so the top level is an object, e.g. {\"twitter_auth_token\": \"...\", \"twitter_ct0\": \"...\"}","Validate with: python -c \"import json;print(type(json.load(open('<path>'))))\" — it must print <class 'dict'>","If the file is expendable, delete it and let agent-reach regenerate it via _sync_xfetch_session"],"exampleFix":"// before: ~/.config/xfetch/session.json\n[ {\"twitter_auth_token\": \"a\", \"twitter_ct0\": \"b\"} ]\n\n// after\n{ \"twitter_auth_token\": \"a\", \"twitter_ct0\": \"b\" }","handlingStrategy":"type-guard","validationCode":"import json\ndata = json.loads(Path('~/.config/xfetch/session.json').expanduser().read_text())\nif not isinstance(data, dict):\n    raise ValueError('session.json must hold a JSON object')","typeGuard":"def is_valid_session_file(path) -> bool:\n    try:\n        return isinstance(json.loads(Path(path).read_text()), dict)\n    except (OSError, json.JSONDecodeError):\n        return False","tryCatchPattern":"try:\n    session = _load_xfetch_session(path)\nexcept ValueError as e:\n    if 'JSON object' in str(e):\n        Path(path).unlink(missing_ok=True)  # regenerate from config\n    else:\n        raise","preventionTips":["Never hand-edit session.json into a list","Let _sync_xfetch_session own writes to that file","Validate the file after any tool touches it"],"tags":["json","session-file","config","legacy-compat"],"backgroundTag":null,"analyzedSha":"93ae1d18c37b707dec053c7c4f9d91cd8ef8943d","analyzedAt":"2026-08-14T22:54:06.735Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}