Skip to content

fix(core): select built-in shell and apply_patch tools by their own type - #4952

Open
subhashpolisetti wants to merge 3 commits into
openai:mainfrom
subhashpolisetti:fix/builtin-tool-choice-shell-apply-patch
Open

fix(core): select built-in shell and apply_patch tools by their own type#4952
subhashpolisetti wants to merge 3 commits into
openai:mainfrom
subhashpolisetti:fix/builtin-tool-choice-shell-apply-patch

Conversation

@subhashpolisetti

Copy link
Copy Markdown
Contributor

Summary

This pull request fixes tool_choice="shell" and tool_choice="apply_patch" selecting a function tool the request never defines.

An agent configured with ShellTool or ApplyPatchTool sends the tool as {"type": "shell"} or {"type": "apply_patch"}. Setting tool_choice to the matching string falls through to the named-function branch, so the same request also carries {"type": "function", "name": "shell"} while no function tool by that name is present in it. The Responses API defines ToolChoiceShellParam and ToolChoiceApplyPatchParam for these tools and ModelSettings.tool_choice accepts the strings, but neither selector is reachable today: required cannot target one specific tool when others are configured, and passing the selector dict directly raises TypeError.

Both strings now map to their own selector when the matching built-in tool is configured. Gating on tool presence follows ComputerTool, where the built-in selector applies only when that tool is present and the string otherwise stays an ordinary function name; without the gate a function tool named shell or apply_patch would silently stop being targetable. LocalShellTool is deliberately unchanged, since the Responses API defines no local_shell selector.

Test plan

  • test_convert_tool_choice_builtin_shell_and_apply_patch asserts each string maps to its own selector when the matching tool is configured. It fails on main, which returns the named-function form.
  • test_convert_tool_choice_allows_function_named_shell_without_builtin_tool pins that a function tool of either name still resolves to the named-function form, mirroring the existing ComputerTool test.
  • make format, make lint, make typecheck and uv run mypy --platform win32 src are clean.
  • .agents/skills/code-change-verification/scripts/run.sh does not complete in my environment: 14 tests in tests/test_code_change_verification_runner.py fail with Timed out waiting for a controlled process transition. They reproduce identically on unmodified main here and are unrelated to this change. Excluding that file, the suite reports 9671 passed, 32 skipped.

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

… type

A request configured with ShellTool or ApplyPatchTool advertises the tool as
{"type": "shell"} or {"type": "apply_patch"}, but tool_choice="shell" and
tool_choice="apply_patch" fell through to the named-function branch and selected
a function tool the same request never defines.

Map both strings to their Responses selector when the matching built-in tool is
configured, and keep the named-function behavior otherwise so a function tool of
the same name still resolves.
@seratch seratch changed the title fix(models): select built-in shell and apply_patch tools by their own type fix(core): select built-in shell and apply_patch tools by their own type Sep 11, 2026

@seratch seratch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the contribution. The selector mismatch is real, and fixing it in the shared Responses converter is appropriate.

Before merging, please preserve named-function and handoff selection when a matching callable target is configured alongside the built-in. For example, with both ShellTool and a function named shell, the released behavior selects the function, but this patch silently selects the built-in. The same issue applies to apply_patch. Give the existing callable target precedence and use the built-in selector when no such target exists.

Please add outgoing-request regression coverage for these coexistence cases and for selecting each built-in among multiple tools. The current tests cover functions only when the corresponding built-in is absent.

…lector

Give a function tool or handoff of the same name precedence over the shell and
apply_patch selectors, and use the built-in selector only when no such callable
target is configured.

Cover the coexistence cases and built-in selection among multiple tools at the
outgoing request.
@subhashpolisetti

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Updated to give callable targets precedence: convert_tool_choice now uses the shell and apply_patch selectors only when no function tool, custom tool, or handoff of that name is configured. I verified each case against v0.22.2, so the released selection is preserved wherever such a target exists. Namespaced and deferred targets still route through the existing named-choice validation unchanged.

Outgoing-request coverage is in tests/models/test_openai_responses.py, covering coexistence with a same-named function tool, custom tool, and handoff, and selecting each built-in among multiple tools. Six of the eight new tests fail on the previous commit.

@subhashpolisetti
subhashpolisetti marked this pull request as ready for review September 11, 2026 04:13

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 235a984258

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/models/openai_responses.py Outdated
Comment on lines +1970 to +1972
return not any(
isinstance(tool, FunctionTool | CustomTool) and tool.name == tool_choice
for tool in tools or ()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve the actual target type before shadowing built-ins

When a built-in coexists with a same-named CustomTool, namespaced FunctionTool, or deferred FunctionTool, this predicate suppresses the valid built-in selector. The fallback then always emits {"type":"function","name":...}; a custom tool is advertised as type custom, while namespace/deferred cases are rejected by _validate_named_function_tool_choice, so the request either targets a nonexistent function or raises instead of forcing the available built-in. Only an exposed top-level function/handoff should select the function form, and other target types need their own valid selector handling.

AGENTS.md reference: AGENTS.md:L100-L102

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed the custom-tool case: a custom tool is advertised as custom, so the named-function selector cannot target it. Precedence is now limited to function tools and handoffs.

Deferred and namespaced function tools are unchanged. Both already raise from _validate_named_function_tool_choice on v0.22.2, so forcing the built-in there would change released behavior beyond this fix.

…s its name

A custom tool is advertised as a custom type, so the named-function selector
cannot target it. Treating it as a callable target suppressed a valid built-in
selector and left the request pointing at a function the request never defines.

Limit the precedence rule to function tools and handoffs, which the named
selector can target, and cover the custom-tool case at the outgoing request.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants