Repository navigation
Conversation
…orm (NousResearch#93650) responses.create re-walks the entire request body against the ResponseCreateParams union graph client-side while holding the GIL. NousResearch#93650 documents that walk wedging for 12+ hours on a ~1.4 MB conversation, starving every other thread including the TTFB/stale watchdogs — and no socket kill can unblock a pre-network hang. Hermes payloads are JSON round-trips and already wire format, so the bulk fields (input, tools) are now routed through extra_body, which the SDK merges into the JSON body after the transform. Guarded by a plain-JSON check (anything else keeps the typed path) and a HERMES_CODEX_SDK_TRANSFORM=1 escape hatch. Applied to both the primary stream path and the auxiliary adapter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Merged via #93773. Your commits were cherry-picked with authorship preserved. What this PR does: Routes the bulk payload fields ( Salvage notes:
Thank you for the thorough forensic analysis in #93650 and the well-crafted fix. The |
and7777
pushed a commit
to and7777/hermes-agent
that referenced
this pull request
Aug 27, 2026
melon-xf
added a commit
to melon-xf/hermes-agent
that referenced
this pull request
Sep 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #93650.
Problem
A
codex_responsescall can freeze the entire agent for hours before a single byte leaves the process. The OpenAI SDK'sresponses.createre-walks the whole request body against theResponseCreateParamsunion graph (openai/_utils/_transform.py::maybe_transform) client-side, holding the GIL.In the incident documented in #93650 (openai 2.24.0, CPython 3.11.16), one such walk over a ~1.4 MB / 1,093-item conversation wedged for 12.5 CPU-hours at ~99% of a core. Core-dump forensics (pystack + gdb, full stacks in the issue):
maybe_transform→isinstance(obj, Mapping)→typing.__subclasscheck__;take_gilfutex waits — including the TTFB/stale watchdog poller, parked att.join(timeout=0.3), which never got to run any of its detectors;list_iteratorout of the core showed the walk mid-list (item 513 of 1,093) after 12.5h — progressing at ~88 s/item, vs 0.35 s for the whole payload in a fresh interpreter.No in-process watchdog can rescue this: the hang starves them via the GIL, and even when they run their remedy is socket-level (
_close_request_client_once) — a pre-network hang has no socket to kill, andThread.joincan't cancel the worker. The agent froze silently mid-task; in that incident it happened seconds before a scheduled Vast.ai instance teardown, which then billed all night.Fix
Hermes assembles Codex payloads from JSON round-trips — they are already wire format, so the transform walk is pure overhead plus this failure mode. The SDK merges
extra_bodyinto the JSON body after the transform (_base_client._build_request, shallow merge), a mechanism this codebase already relies on forprompt_cache_retention. This PR routes the bulk payload fields (input,tools) throughextra_body, skippingmaybe_transformfor them entirely while producing an identical request body.Safety rails:
dict/list/str/int/float/bool/Nonewithstrkeys). Pydantic models, generators, or anything else keep the typed SDK path, which still needs the transform. The guard is a single cheap C-levelisinstancewalk (~tens of ms on multi-MB payloads, less than the transform it replaces).extra_bodyentry wins over a moved field, matching what the SDK's own post-transform merge would have resolved.HERMES_CODEX_SDK_TRANSFORM=1restores the pre-fix behavior.Applied at both live
responses.createcall sites: the primary stream path (codex_runtime._open_codex_stream) and the auxiliary adapter (auxiliary_client._CodexCompletionsAdapter), whose approval calls carry ~300K-token contexts through the same walk.Tests
tests/run_agent/test_codex_sdk_transform_bypass.py(10 tests): field movement, extra_body merge/precedence, non-JSON fallback to the typed path, string-input passthrough, env escape hatch, and an end-to-endrun_codex_streamassertion thatcreatereceives the payload viaextra_body.test_run_agent_codex_responses.py::test_run_codex_stream_strips_nested_request_override_retentionupdated for the new wire shape (its contract — retention never crosses the wire — is asserted more precisely now).test_native_compaction.pyfake-client capture updated to accept the payload in either wire shape.tests/run_agent/suite: no regressions vsmain(7 pre-existing environment-dependent failures reproduce identically on a cleanmainworktree; details on request).Follow-up (out of scope here)
A process-external liveness check (e.g. gateway-side "worker thread >N min at ~100% CPU with no log progress → restart TUI") would harden against any future GIL-holding client-side hang, since no in-process watchdog can be trusted against that class. Happy to file separately.
🤖 Generated with Claude Code