ZhuLinsen/daily_stock_analysis · error · CodexAppServerError

protocol_error

protocol_error

Error message

App Server returned a non-object result for {method}

What it means

After a successful JSON-RPC response, request() requires message['result'] to be a dict. If the app-server returns a result that is null, a list, a string, or a number, the transport raises code 'protocol_error'. It guards every caller that then does result.get(...), so any non-object result is treated as a broken server contract.

Source

Thrown at src/agent/codex_app_server_transport.py:388

                with self._state_lock:
                    self._pending.pop(request_id, None)
                self._terminate_process()
                raise CodexAppServerError("timeout", f"App Server request timed out: {method}")
            try:
                message = response_queue.get(timeout=min(remaining, 0.1))
                break
            except queue.Empty:
                continue
        if "error" in message:
            error = message.get("error") or {}
            safe_message = redact_diagnostic_value(
                error.get("message", "App Server request failed"),
                limit=500,
            )
            raise CodexAppServerError("protocol_error", safe_message)
        result = message.get("result")
        if not isinstance(result, dict):
            raise CodexAppServerError(
                "protocol_error",
                f"App Server returned a non-object result for {method}",
            )
        return result

    def notify(self, method: str, params: dict) -> None:
        deadline = time.monotonic() + self.request_timeout
        if self.deadline is not None:
            deadline = min(deadline, self.deadline)
        try:
            self._write_message(
                {"method": method, "params": params},
                deadline=deadline,
            )
        except CodexAppServerError as exc:
            if exc.code in {"cancelled", "timeout"}:
                self._terminate_process()
            raise

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Pin the Codex App Server binary version this transport was built against and restore it if it drifted
  2. Log the raw frame (message before the check) at debug level to see exactly what shape came back and for which method
  3. Check the Codex protocol changelog for the failing method and align the transport layer
  4. If you control the server side, ensure every request response carries an object-typed result field

Example fix

# before (server side / mock)
def handle_config_read(req):
    return ["layer1"]  # list result -> protocol_error in client

# after
def handle_config_read(req):
    return {"config": {"features": {}, "mcp_servers": {}}}  # object envelope
Defensive patterns

Strategy: try-catch

Type guard

def is_object_result(result: object) -> bool:
    return isinstance(result, dict)

Try / catch

try:
    result = client.request(method, params)
except CodexAppServerError as exc:
    if exc.code == "protocol_error":
        log_raw_frame(method)
        alert_version_mismatch(method)
    raise

Prevention

When it happens

Trigger: A Codex App Server version change alters the response envelope for a method (e.g. returns a bare array or null on success); a proxy or wrapper between client and server mangles the frame; the server replies with an error-free message but omits 'result' entirely.

Common situations: Upgrading the codex binary to a version with a changed protocol; running against an experimental or forked app-server; protocol drift between the pinned transport implementation and the installed server.

Related errors


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/c5f0f2742304be86. Report an issue: GitHub.