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()
raiseView on GitHub (pinned to 5159bd72e8)
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
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
- 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
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.