Skip to content

fix(approvals): handle JSON recursion limits - #4773

Open
sylvesterkaczmarek wants to merge 1 commit into
openai:mainfrom
sylvesterkaczmarek:fix/approval-json-recursion
Open

fix(approvals): handle JSON recursion limits#4773
sylvesterkaczmarek wants to merge 1 commit into
openai:mainfrom
sylvesterkaczmarek:fix/approval-json-recursion

Conversation

@sylvesterkaczmarek

Copy link
Copy Markdown
Contributor

Summary

This pull request keeps function-tool approval argument parsing on its documented best-effort path when Python's JSON decoder hits its recursion limit.

parse_function_tool_arguments() returns parsed object arguments or None when an approval policy cannot inspect them. It already treats malformed JSON and non-standard constants that way by catching ValueError, but sufficiently deeply nested model-supplied JSON causes json.loads() to raise RecursionError instead. That exception currently escapes approval handling in both the normal runner and Realtime paths.

The fix catches RecursionError alongside ValueError. Valid JSON behaviour and the existing object-only return contract are unchanged.

Test plan

  • Added a focused regression that makes the JSON decoder raise RecursionError and verifies parse_function_tool_arguments() returns None.
  • The test patches the decoder rather than depending on an interpreter-specific recursion threshold.

Issue number

Closes #4772

Checks

  • I've added new tests, if relevant
  • I've run .agents/skills/code-change-verification/scripts/run.sh
  • I've confirmed all verification steps pass
  • If using Codex, I've run /review before submitting this PR

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-29T21:24:56.964582Z 06c55c7 PR opened
🔒 Security Review Completed 2026-08-29T21:27:48.739197Z 06c55c7 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

Copy link
Copy Markdown

There is an earlier json.loads() on the normal Runner path that can raise before parse_function_tool_arguments() is reached.

I reproduced this by adding a deeply nested payload to test_callable_function_approval_fails_closed_for_invalid_arguments. With the approval-parser catch applied, the run still fails in agents._tool_invocation._normalize_arguments() via tool_planning -> tool_invocation_identity:

RecursionError: maximum recursion depth exceeded while decoding a JSON object from a unicode string

_normalize_arguments() currently catches TypeError, ValueError, and JSONDecodeError, but not RecursionError. Adding RecursionError to that fallback as well makes the existing Runner-level fail-closed test pass without invoking either the approval callback or the tool. I verified the focused approval cases plus all 126 tests in tests/test_tool_approval_call_id_reuse.py.

@linhongyu510 linhongyu510 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the exact head 06c55c78 against the real decoder failure and the shared approval paths.

On base 89c02c82, an object payload nested to 4 * sys.getrecursionlimit() raises RecursionError: maximum recursion depth exceeded while decoding a JSON object from a unicode string. On this head, the same payload returns None, which keeps both callers fail-closed: the normal runner and Realtime paths interpret an uninspectable callable-policy argument as requiring approval.

I also checked the surrounding parser matrix: a valid object still returns its dict, while malformed JSON, NaN, a top-level list, None, and blank input all return None. Focused validation in the frozen environment:

pytest -q tests/test_approval_utils.py tests/test_run_context_approvals.py tests/realtime/test_session.py -k approval
51 passed, 179 deselected

Ruff check, Ruff format, and git diff --check pass for the two changed files. No functional blocker found.

Non-blocking test improvement: consider adding the issue's real deeply nested JSON payload (derived from sys.getrecursionlimit()) alongside or instead of monkeypatching json.loads. The current mock proves the catch branch, while the real payload also locks the concrete stdlib decoder behavior that made this reachable. At review time this SHA has no remote check runs, so the results above are local evidence rather than a claim that upstream CI is green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Deeply nested tool arguments can crash approval parsing with RecursionError

3 participants