{"record":{"id":"6996322153af8851","repo":"XX-net/XX-Net","slug":"api-s-parse-json-s-fail-r","errorCode":null,"errorMessage":"api:%s parse json:%s fail:%r","messagePattern":"api:(.+?) parse json:(.+?) fail:%r","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"code/default/x_tunnel/local/proxy_session.py","lineNumber":1208,"sourceCode":"                break\n\n        time_cost = time.time() - start_time\n        if status != 200:\n            reason = \"status:%r\" % status\n            xlog.warn(\"api:%s fail:%s t:%d\", path, reason, time_cost)\n            g.last_api_error = reason\n            return False, reason\n\n        content = decrypt_data(content)\n        if isinstance(content, memoryview):\n            content = content.tobytes()\n\n        content = utils.to_str(content)\n        try:\n            info = json.loads(content)\n        except Exception as e:\n            g.last_api_error = \"parse json fail\"\n            xlog.warn(\"api:%s parse json:%s fail:%r\", path, content, e)\n            return False, \"parse json fail\"\n\n        res = info[\"res\"]\n        if res != \"success\":\n            g.last_api_error = info[\"reason\"]\n            xlog.warn(\"api:%s fail:%s\", path, info[\"reason\"])\n            return False, info[\"reason\"]\n\n        xlog.info(\"api:%s success t:%d\", path, time_cost * 1000)\n        g.last_api_error = \"\"\n        return True, info\n    except Exception as e:\n        xlog.exception(\"order e:%r\", e)\n        g.last_api_error = \"%r\" % e\n        return False, \"except:%r\" % e\n\n\ncenter_login_process = False","sourceCodeStart":1190,"sourceCodeEnd":1226,"githubUrl":"https://github.com/XX-net/XX-Net/blob/cfa5bc17b67676e467f37ec50766127e0ab5f0aa/code/default/x_tunnel/local/proxy_session.py#L1190-L1226","documentation":"The control-plane API returned HTTP 200 but the (decrypted) body is not valid JSON. g.last_api_error is set to 'parse json fail' and call_api returns (False, 'parse json fail').","triggerScenarios":"Server returns HTML error page, plain-text message, or empty body with 200; decryption produced garbage due to key/session mismatch; content truncated by proxies.","commonSituations":"Captive portal or intercepting proxy rewriting responses, wrong session key after re-login, server bug or format change.","solutions":["Check the logged raw content to see what was actually returned (HTML page → interception; garbage → key mismatch)","Re-login / restart to re-establish session encryption keys","Bypass interfering middleboxes or change network","Report server format change if content is consistently non-JSON"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":"def looks_like_api_json(content: bytes) -> bool:\n    s = utils.to_str(content).lstrip()\n    return s.startswith('{') or s.startswith('[')","tryCatchPattern":"try:\n    info = json.loads(content)\nexcept Exception:\n    return False, 'parse json fail'  # keep session; retry later","preventionTips":["Detect HTML/intercepted bodies and trigger re-login","Don't assume 200 implies JSON; validate content type/shape"],"tags":["x-tunnel","json","api","parsing"],"backgroundTag":"invalid-json-response","analyzedSha":"cfa5bc17b67676e467f37ec50766127e0ab5f0aa","analyzedAt":"2026-08-27T19:28:28.225Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}