{"record":{"id":"cb98b1f895871c78","repo":"NousResearch/hermes-agent","slug":"32603","errorCode":"-32603","errorMessage":"codex thread/start returned no thread id (payload keys: {sorted(result.keys())})","messagePattern":"codex thread/start returned no thread id \\(payload keys: (.+?)\\)","errorType":"exception","errorClass":"CodexAppServerError","httpStatus":null,"severity":"error","filePath":"agent/transports/codex_app_server_session.py","lineNumber":359,"sourceCode":"        # configured otherwise in their codex config.toml) is the standard\n        # codex CLI workflow and avoids fighting codex's own validation.\n        # Users who want a write-capable profile configure it in their\n        # ~/.codex/config.toml the same way they would for any codex usage.\n        params: dict[str, Any] = {\"cwd\": self._cwd}\n        result = self._client.request(\"thread/start\", params, timeout=15)\n        # Cross-fill thread.id/sessionId — different codex versions have\n        # serialized this under either key. Mirrors openclaw beta.8's\n        # tolerance fix so future codex drops/renames don't KeyError us\n        # at handshake time.\n        thread_obj = result.get(\"thread\") or {}\n        thread_id = (\n            thread_obj.get(\"id\")\n            or thread_obj.get(\"sessionId\")\n            or result.get(\"sessionId\")\n            or result.get(\"threadId\")\n        )\n        if not thread_id:\n            raise CodexAppServerError(\n                code=-32603,\n                message=(\n                    \"codex thread/start returned no thread id \"\n                    f\"(payload keys: {sorted(result.keys())})\"\n                ),\n            )\n        self._thread_id = thread_id\n        logger.info(\n            \"codex app-server thread started: id=%s profile=%s cwd=%s\",\n            self._thread_id[:8],\n            self._permission_profile,\n            self._cwd,\n        )\n        return self._thread_id\n\n    def close(self) -> None:\n        if self._closed:\n            return","sourceCodeStart":341,"sourceCodeEnd":377,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/agent/transports/codex_app_server_session.py#L341-L377","documentation":"CodexAppServerError (code -32603, internal error) from the thread/start handling in agent/transports/codex_app_server_session.py:359. The session layer tolerates several codex serialization variants (thread.id, thread.sessionId, top-level sessionId, threadId) but if none of them yields an id it refuses to continue, reporting which keys the payload actually had so the mismatch is diagnosable.","triggerScenarios":"A codex app-server version whose thread/start response schema moved the thread id to a new key not in the tolerated list (or omits it entirely on failure), typically right after upgrading/downgrading codex.","commonSituations":"Version skew: the pinned codex version changed its JSON-RPC payload shape; a partially-failed thread/start that returns an error-ish dict without an id; nightly/rolling codex installs.","solutions":["Read the payload keys in the message and compare against the four tolerated keys (id/sessionId/threadId) to confirm the schema moved.","Pin the codex app-server to a version this client is tested against (or upgrade hermes-agent to a release supporting the new schema).","If you maintain a fork, add the new key to the cross-fill chain in codex_app_server_session.py and upstream it.","Check codex's own logs for why thread/start may have returned a degenerate payload (e.g. auth/config error)."],"exampleFix":"# before (tolerated keys only)\nthread_id = (\n    thread_obj.get(\"id\") or thread_obj.get(\"sessionId\")\n    or result.get(\"sessionId\") or result.get(\"threadId\")\n)\n\n# after (extend for a newer codex schema)\nthread_id = (\n    thread_obj.get(\"id\") or thread_obj.get(\"sessionId\")\n    or result.get(\"sessionId\") or result.get(\"threadId\")\n    or result.get(\"conversationId\")\n)","handlingStrategy":"try-catch","validationCode":"_TOLERATED_KEYS = (\"thread\", \"sessionId\", \"threadId\")\n\ndef thread_start_payload_looks_ok(result: dict) -> bool:\n    t = result.get(\"thread\") or {}\n    return bool(t.get(\"id\") or t.get(\"sessionId\") or result.get(\"sessionId\") or result.get(\"threadId\"))","typeGuard":null,"tryCatchPattern":"from agent.transports.codex_app_server import CodexAppServerError\n\ntry:\n    session.start_thread()\nexcept CodexAppServerError as exc:\n    if exc.code == -32603 and \"no thread id\" in exc.message:\n        raise RuntimeError(\n            f\"codex schema mismatch (keys: {exc.message}); pin/upgrade codex\"\n        ) from exc\n    raise","preventionTips":["Pin codex to a version tested with this client.","Include the payload-keys detail from the message in bug reports.","After any codex upgrade, smoke-test thread/start before rolling out."],"tags":["codex","jsonrpc","version-skew","schema"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}