Skip to content

fix(codex): omit tools key from Codex Responses kwargs when no tools registered#33409

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-5bf34d29
May 27, 2026
Merged

fix(codex): omit tools key from Codex Responses kwargs when no tools registered#33409
teknium1 merged 1 commit into
mainfrom
hermes/hermes-5bf34d29

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Salvage of the transport-side fix from #32911 (@xxxigm). Closes #32892.

Why

The openai SDK's responses.stream() / responses.parse() eagerly call _make_tools(tools), which iterates tools without a None guard. Passing tools=None raises TypeError: 'NoneType' object is not iterable before any HTTP request is issued (openai==2.24.0).

#33042 already removed responses.stream() from our own Codex call paths, so the specific iteration crash inside _make_tools is no longer on the hot path. But the right API contract is to omit tools entirely when there are no functions to expose — passing tools=None to the backend is semantically wrong regardless of the SDK's iteration behavior, and we'd hit it again on any future code path that hasn't migrated off responses.stream().

Changes

  • agent/transports/codex.py: move 'tools': response_tools into the if response_tools: branch so the key is omitted when there are no tools (mirrors how tool_choice and parallel_tool_calls are already handled).
  • tests/run_agent/test_codex_no_tools_nonetype.py: 6 tests covering the transport invariant + SDK contract sanity check.

Why not pure cherry-pick

@xxxigm's original PR had two parts:

  1. Transport-level tools omission (this PR — still real and useful)
  2. agent/codex_runtime._strip_sdk_none_iterables defensive shim (obsolete — the SDK helper that needed defending is gone after refactor(codex): drop SDK responses.stream() helper; consume events directly #33042)

This salvages just part 1. The trimmed test file drops the 7 tests for the obsolete shim + recording-stream helper that don't exist on main anymore, keeps the 5 transport behavior tests + the SDK contract sanity check (test_openai_sdk_raises_typeerror_on_tools_none) so we notice if upstream ever fixes _make_tools(None).

Validation

  • tests/run_agent/test_codex_no_tools_nonetype.py → 6/6 passing locally

Attribution

Co-authored by @xxxigm. Authorship preserved via author field + co-author trailer.

…registered

Salvages the transport-side fix from #32911 (@xxxigm). Closes #32892.

The openai SDK's responses.stream() / responses.parse() eagerly call
_make_tools(tools), which iterates tools without a None guard. Passing
tools=None raises TypeError: 'NoneType' object is not iterable before
any HTTP request is issued (openai==2.24.0).

PR #33042 already removed responses.stream() from our own Codex call
paths, so the specific iteration crash inside _make_tools is no longer
on the hot path. But the right API contract is to omit tools entirely
when there are no functions to expose — passing tools=None to the
backend is semantically wrong regardless of the SDK's iteration
behavior, and we'd hit it again on any future code path that hasn't
migrated off responses.stream().

This applies the transport-level part of @xxxigm's fix: move
'tools': response_tools into the if response_tools: branch so the
key is omitted when there are no tools, just like tool_choice and
parallel_tool_calls already are. Skips the run_agent.py-side
_strip_sdk_none_iterables helper from their PR — that path is now
obsolete because the SDK helper that needed defending is gone.

Tests
- tests/run_agent/test_codex_no_tools_nonetype.py: 6 tests trimmed
  from @xxxigm's original 13-test file. Drops the obsolete tests for
  _strip_sdk_none_iterables and _RecordingResponsesStream (helpers
  that don't exist on main anymore), keeps the transport behavior
  tests + the SDK contract sanity check that ensures we notice if
  upstream ever fixes _make_tools(None).
- 6/6 passing locally.

Co-authored-by: xxxigm <tuancanhnguyen706@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-5bf34d29 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9512 on HEAD, 9507 on base (🆕 +5)

🆕 New issues (3):

Rule Count
unresolved-import 2
no-matching-overload 1
First entries
tests/run_agent/test_codex_no_tools_nonetype.py:35: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/run_agent/test_codex_no_tools_nonetype.py:42: [no-matching-overload] no-matching-overload: No overload of bound method `MutableMapping.setdefault` matches arguments
tests/run_agent/test_codex_no_tools_nonetype.py:176: [unresolved-import] unresolved-import: Cannot resolve imported module `openai.resources.responses.responses`

✅ Fixed issues: none

Unchanged: 5006 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@teknium1 teknium1 merged commit fc47b72 into main May 27, 2026
30 of 32 checks passed
@teknium1 teknium1 deleted the hermes/hermes-5bf34d29 branch May 27, 2026 18:46
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent loop, run_agent.py, prompt builder codex provider/openai OpenAI / Codex Responses API P3 Low — cosmetic, nice to have labels May 27, 2026
aaka3207 added a commit to aaka3207/hermes that referenced this pull request May 29, 2026
The Hermes codex transport patch in this Dockerfile moves `tools` into
the `if response_tools:` block so tool-less Codex calls don't send an
empty `tools` field. That exact change shipped upstream in
NousResearch/hermes-agent#33409, which is part of v0.15.0 (2026.5.28)
and therefore already present in `nousresearch/hermes-agent:latest`.

The patch's `if 'kwargs["tools"] = response_tools' in s:` early-return
was already making this RUN block a no-op at build time; this PR drops
the dead code.

The other two patches (openai SDK null-guard, mnemosyne lazy-register)
are kept:
- openai SDK null-guard: upstream fix PR #34544 merged today (May 29)
  but did NOT land in v0.15.2 — keep until v0.15.3.
- mnemosyne lazy-register: separate concern, not yet superseded.
aaka3207 added a commit to aaka3207/hermes that referenced this pull request May 29, 2026
)

The Hermes codex transport patch in this Dockerfile moves `tools` into
the `if response_tools:` block so tool-less Codex calls don't send an
empty `tools` field. That exact change shipped upstream in
NousResearch/hermes-agent#33409, which is part of v0.15.0 (2026.5.28)
and therefore already present in `nousresearch/hermes-agent:latest`.

The patch's `if 'kwargs["tools"] = response_tools' in s:` early-return
was already making this RUN block a no-op at build time; this PR drops
the dead code.

The other two patches (openai SDK null-guard, mnemosyne lazy-register)
are kept:
- openai SDK null-guard: upstream fix PR #34544 merged today (May 29)
  but did NOT land in v0.15.2 — keep until v0.15.3.
- mnemosyne lazy-register: separate concern, not yet superseded.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

codex comp/agent Core agent loop, run_agent.py, prompt builder P3 Low — cosmetic, nice to have provider/openai OpenAI / Codex Responses API type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Error: 'NoneType' object is not iterable

3 participants