{"record":{"id":"c5f0f2742304be86","repo":"ZhuLinsen/daily_stock_analysis","slug":"protocol-error","errorCode":"protocol_error","errorMessage":"App Server returned a non-object result for {method}","messagePattern":"App Server returned a non-object result for (.+?)","errorType":"error_code","errorClass":"CodexAppServerError","httpStatus":null,"severity":"error","filePath":"src/agent/codex_app_server_transport.py","lineNumber":388,"sourceCode":"                with self._state_lock:\n                    self._pending.pop(request_id, None)\n                self._terminate_process()\n                raise CodexAppServerError(\"timeout\", f\"App Server request timed out: {method}\")\n            try:\n                message = response_queue.get(timeout=min(remaining, 0.1))\n                break\n            except queue.Empty:\n                continue\n        if \"error\" in message:\n            error = message.get(\"error\") or {}\n            safe_message = redact_diagnostic_value(\n                error.get(\"message\", \"App Server request failed\"),\n                limit=500,\n            )\n            raise CodexAppServerError(\"protocol_error\", safe_message)\n        result = message.get(\"result\")\n        if not isinstance(result, dict):\n            raise CodexAppServerError(\n                \"protocol_error\",\n                f\"App Server returned a non-object result for {method}\",\n            )\n        return result\n\n    def notify(self, method: str, params: dict) -> None:\n        deadline = time.monotonic() + self.request_timeout\n        if self.deadline is not None:\n            deadline = min(deadline, self.deadline)\n        try:\n            self._write_message(\n                {\"method\": method, \"params\": params},\n                deadline=deadline,\n            )\n        except CodexAppServerError as exc:\n            if exc.code in {\"cancelled\", \"timeout\"}:\n                self._terminate_process()\n            raise","sourceCodeStart":370,"sourceCodeEnd":406,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/agent/codex_app_server_transport.py#L370-L406","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pin the Codex App Server binary version this transport was built against and restore it if it drifted","Log the raw frame (message before the check) at debug level to see exactly what shape came back and for which method","Check the Codex protocol changelog for the failing method and align the transport layer","If you control the server side, ensure every request response carries an object-typed result field"],"exampleFix":"# before (server side / mock)\ndef handle_config_read(req):\n    return [\"layer1\"]  # list result -> protocol_error in client\n\n# after\ndef handle_config_read(req):\n    return {\"config\": {\"features\": {}, \"mcp_servers\": {}}}  # object envelope","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"def is_object_result(result: object) -> bool:\n    return isinstance(result, dict)","tryCatchPattern":"try:\n    result = client.request(method, params)\nexcept CodexAppServerError as exc:\n    if exc.code == \"protocol_error\":\n        log_raw_frame(method)\n        alert_version_mismatch(method)\n    raise","preventionTips":["Pin the codex app-server version and verify it in CI before rollout","Keep contract tests that assert response envelope shapes per method","Log raw frames at debug level so protocol drift is diagnosable in one incident"],"tags":["codex","jsonrpc","protocol","version-compat"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}