XX-net/XX-Net · error

api:%s parse json:%s fail:%r

Error message

api:%s parse json:%s fail:%r

What it means

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').

Source

Thrown at code/default/x_tunnel/local/proxy_session.py:1208

                break

        time_cost = time.time() - start_time
        if status != 200:
            reason = "status:%r" % status
            xlog.warn("api:%s fail:%s t:%d", path, reason, time_cost)
            g.last_api_error = reason
            return False, reason

        content = decrypt_data(content)
        if isinstance(content, memoryview):
            content = content.tobytes()

        content = utils.to_str(content)
        try:
            info = json.loads(content)
        except Exception as e:
            g.last_api_error = "parse json fail"
            xlog.warn("api:%s parse json:%s fail:%r", path, content, e)
            return False, "parse json fail"

        res = info["res"]
        if res != "success":
            g.last_api_error = info["reason"]
            xlog.warn("api:%s fail:%s", path, info["reason"])
            return False, info["reason"]

        xlog.info("api:%s success t:%d", path, time_cost * 1000)
        g.last_api_error = ""
        return True, info
    except Exception as e:
        xlog.exception("order e:%r", e)
        g.last_api_error = "%r" % e
        return False, "except:%r" % e


center_login_process = False

View on GitHub (pinned to cfa5bc17b6)

Solutions

  1. Check the logged raw content to see what was actually returned (HTML page → interception; garbage → key mismatch)
  2. Re-login / restart to re-establish session encryption keys
  3. Bypass interfering middleboxes or change network
  4. Report server format change if content is consistently non-JSON
Defensive patterns

Strategy: fallback

Type guard

def looks_like_api_json(content: bytes) -> bool:
    s = utils.to_str(content).lstrip()
    return s.startswith('{') or s.startswith('[')

Try / catch

try:
    info = json.loads(content)
except Exception:
    return False, 'parse json fail'  # keep session; retry later

Prevention

When it happens

Trigger: 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.

Common situations: Captive portal or intercepting proxy rewriting responses, wrong session key after re-login, server bug or format change.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of XX-net/XX-Net@cfa5bc17b6 (2026-08-27). Data as JSON: /api/errors/6996322153af8851. Report an issue: GitHub.